Common Intermediate Language и системное программирование в Microsoft.Net. Макаров А.В - 122 стр.

UptoLike

для него процессор (по умолчанию – тот, на котором поток был запущен
первый раз). Для этого предназначена функция SetThreadIdealProcessor.
При использовании NUMA систем следует учитывать, что распреде-
ление доступных процессоров по узлам NUMA системы не обязательно
последовательное – узлы со смежными номерами могут быть с аппарат-
ной точки зрения весьма удалены друг от друга. Функция
GetNumaHighestNodeNumber позволяет определить число NUMA узлов, пос-
ле чего с помощью обращений к функциям GetNumaProcessorNode,
GetNumaNodeProcessorMask и GetNumaAvailableMemoryNode можно определить
размещение узлов NUMA системы на процессорах и доступную каждому
узлу память.
7.2. Взаимодействие процессов и потоков
Материалы, рассматриваемые в этом разделе, могут быть разделены
на две категории – синхронизация потоков и работа с процессами. Они
сгруппированы в силу того, что большая часть методов синхронизации по-
токов может быть успешно применена для потоков, принадлежащих раз-
ным процессам. Кроме того, многие средства взаимодействия потоков,
даже основанные на совместном доступе к данным в едином адресном
пространстве, могут быть использованы в рамках одного вычислительно-
го узла – в случае применения общей, разделяемой процессами, памяти.
7.2.1. Синхронизация потоков
При реализации мультипрограммирования существует проблема од-
новременного конкурирующего доступа нескольких потоков к общим
разделяемым данным. В многопоточных приложениях она особенно акту-
альна, так как вся память процесса является общей и разделяемой всеми
потоками, поэтому конфликты при одновременном доступе могут возни-
кать достаточно часто.
Рассмотрим простейшую программу, в которой несколько потоков
увеличивают на 1 значение элементов общего массива. Так как начальные
значения элементов массива 0, то в результате весь массив должен быть за-
полнен числами, соответствующими числу потоков:
#include <stdio.h>
#include <process.h>
#include <windows.h>
#define THREADS 10
#define ASIZE 10000000
static LONG array[ASIZE];
Разработка параллельных приложений для ОС Windows
231
DWORD dwFlsID;
VOID WINAPI FlsCallback( PVOID lpFlsData )
{
/* при завершении волокна или потока память будет освобождена */
delete[] (int*)lpFlsData;
}
void initialize( void )
{
dwFlsID = FlsAlloc( FlsCallback );
...
}
void fiberstart( void )
{
FlsSetValue( dwFlsID, new int [ 100 ] );
/* здесь мы можем не следить за освобождением выделенной памяти */
}
Остальные функции для работы с FLS аналогичны Tls-функциям как
по описаниям, так и по применению.
7.1.3. Привязка к процессору и системы с неоднородным
доступом к памяти
ОС Windows предоставляет небольшой набор функций, предназна-
ченных для поддержки систем с неоднородным доступом к памяти
(NUMA). К таким функциям относятся средства, обеспечивающие вы-
полнение потоков на конкретных процессорах, и функции, позволяющие
получить информацию о структуре NUMA машины. В некоторых случаях
привязка потоков к процессорам может преследовать и иные цели, чем
поддержка NUMA архитектуры. Так, например, привязка потока к про-
цессору может улучшить использование кэша; на некоторых SMP маши-
нах могут возникать проблемы с использованием таймеров высокого раз-
решения (опирающихся на счетчики процессоров) и т.д.
Привязка потоков к процессору задается с помощью специального
битового вектора (affinity mask), сохраняемого в целочисленной перемен-
ной. Каждый бит этого вектора указывает на возможность исполнения по-
тока на процессоре, номер которого совпадает с номером бита. Таким об-
разом, заданием маски сродства можно ограничить множество процессо-
ров, на которых будет выполняться данный поток. В Windows такие маски
назначаются процессу (функции GetProcessAffinityMask и
SetProcessAffinityMask) и потоку (функция SetThreadAffinityMask). Мас-
ка, назначаемая потоку, должна быть подмножеством маски процесса. По-
мимо ограничения множества процессоров, на которых может исполнять-
ся поток, может быть целесообразно назначить потоку самый «удобный»
230
CIL и системное программирование в Microsoft .NET
230                         CIL и системное программирование в Microsoft .NET   Разработка параллельных приложений для ОС Windows                    231


     DWORD dwFlsID;                                                             для него процессор (по умолчанию – тот, на котором поток был запущен
     VOID WINAPI FlsCallback( PVOID lpFlsData )                                 первый раз). Для этого предназначена функция SetThreadIdealProcessor.
     {                                                                               При использовании NUMA систем следует учитывать, что распреде-
       /* при завершении волокна или потока память будет освобождена */         ление доступных процессоров по узлам NUMA системы не обязательно
       delete[] (int*)lpFlsData;                                                последовательное – узлы со смежными номерами могут быть с аппарат-
     }                                                                          ной точки зрения весьма удалены друг от друга. Функция
     void initialize( void )                                                    GetNumaHighestNodeNumber позволяет определить число NUMA узлов, пос-
     {                                                                          ле чего с помощью обращений к функциям GetNumaProcessorNode,
       dwFlsID = FlsAlloc( FlsCallback );                                       GetNumaNodeProcessorMask и GetNumaAvailableMemoryNode можно определить
       ...                                                                      размещение узлов NUMA системы на процессорах и доступную каждому
     }                                                                          узлу память.
     void fiberstart( void )
     {
       FlsSetValue( dwFlsID, new int [ 100 ] );                                 7.2. Взаимодействие процессов и потоков
       /* здесь мы можем не следить за освобождением выделенной памяти */
     }                                                                               Материалы, рассматриваемые в этом разделе, могут быть разделены
     Остальные функции для работы с FLS аналогичны Tls-функциям как             на две категории – синхронизация потоков и работа с процессами. Они
по описаниям, так и по применению.                                              сгруппированы в силу того, что большая часть методов синхронизации по-
                                                                                токов может быть успешно применена для потоков, принадлежащих раз-
7.1.3. Привязка к процессору и системы с неоднородным                           ным процессам. Кроме того, многие средства взаимодействия потоков,
доступом к памяти                                                               даже основанные на совместном доступе к данным в едином адресном
     ОС Windows предоставляет небольшой набор функций, предназна-               пространстве, могут быть использованы в рамках одного вычислительно-
ченных для поддержки систем с неоднородным доступом к памяти                    го узла – в случае применения общей, разделяемой процессами, памяти.
(NUMA). К таким функциям относятся средства, обеспечивающие вы-
полнение потоков на конкретных процессорах, и функции, позволяющие              7.2.1. Синхронизация потоков
получить информацию о структуре NUMA машины. В некоторых случаях                     При реализации мультипрограммирования существует проблема од-
привязка потоков к процессорам может преследовать и иные цели, чем              новременного конкурирующего доступа нескольких потоков к общим
поддержка NUMA архитектуры. Так, например, привязка потока к про-               разделяемым данным. В многопоточных приложениях она особенно акту-
цессору может улучшить использование кэша; на некоторых SMP маши-               альна, так как вся память процесса является общей и разделяемой всеми
нах могут возникать проблемы с использованием таймеров высокого раз-            потоками, поэтому конфликты при одновременном доступе могут возни-
решения (опирающихся на счетчики процессоров) и т.д.                            кать достаточно часто.
     Привязка потоков к процессору задается с помощью специального                   Рассмотрим простейшую программу, в которой несколько потоков
битового вектора (affinity mask), сохраняемого в целочисленной перемен-         увеличивают на 1 значение элементов общего массива. Так как начальные
ной. Каждый бит этого вектора указывает на возможность исполнения по-           значения элементов массива 0, то в результате весь массив должен быть за-
тока на процессоре, номер которого совпадает с номером бита. Таким об-          полнен числами, соответствующими числу потоков:
разом, заданием маски сродства можно ограничить множество процессо-                  #include 
ров, на которых будет выполняться данный поток. В Windows такие маски                #include 
назначаются      процессу      (функции      GetProcessAffinityMask   и              #include 
SetProcessAffinityMask) и потоку (функция SetThreadAffinityMask). Мас-
ка, назначаемая потоку, должна быть подмножеством маски процесса. По-                #define THREADS 10
мимо ограничения множества процессоров, на которых может исполнять-                  #define ASIZE 10000000
ся поток, может быть целесообразно назначить потоку самый «удобный»                  static LONG   array[ASIZE];