АОПИ. Лекции / 6
.pdfОжидание освобождения нескольких объектов может быть организовано с помощью функции:
DWORD WaitForMultipleObjects(IN DWORD nCount, IN CONST HANDLE *lpHandles, IN BOOL bWaitAll, IN DWORD dwMilliseconds);
где lpHandles – указатель на массив дескрипторов объектов, nCount – число дескрипторов в этом массиве, а параметр bWaitAll определяет логику ожидания. При bWaitAll = true функция ждёт, пока все объекты окажутся в сигнальном состоянии. При bWaitAll = false функция завершает свою работу, если свободен хотя бы один объект. В этом случае возвращаемое функцией значение указывает, какой именно объект находится в этом состоянии.
Функция отличается от функции WaitForSingleObject возвращаемыми значениями. При bWaitAll = true функция возвращает значения WAIT_OBJECT_0, WAIT_TIMEOUT, WAIT_ABANDONED_0 или WAIT_FAILED. При bWaitAll = false возможные значения WAIT_TIMEOUT, WAIT_FAILED, от WAIT_OBJECT_0 до WAIT_OBJECT_0 + nCount – 1 и от WAIT_ABANDONED_0 до WAIT_ABANDONED_0 + nCount – 1. Разность возвращённого результата – WAIT_OBJECT_0 или возвращённого результата – WAIT_ABANDONED_0 дают индекс того объекта в массиве lpHandles, который вызвал завершение выполнения функции.
§3. Синхронизация задач с помощью объектов ядра «события».
События являются самыми примитивными объектами ядра, по сути простыми уведомлениями об окончании каких-либо действий. Событие содержит счетчик числа пользователей и две логические переменные: тип события и состояние (свободен или занят).
Различают два типа событий:
−события с ручным сбросом;
−события с автоматическим сбросом.
Событие с ручным сбросом можно перевести в несигнальное состояние только вызовом функции ResetEvent. Событие с автоматическим сбросом переводится в несигнальное состояние как с помощью функции ResetEvent, так и с помощью функции ожидания, то есть когда поток успешно дождался такого события, он автоматически сбрасывается в несигнальное (занятое) состояние.
Примечание. Если события с автоматическим сбросом ожидают несколько потоков с помощью функции WaitForSingleObject, то из состояния ожидания освобождается только один из этих потоков.
Событие создается функцией
HANDLE CreateEvent(IN LPSECURITY_ATTRIBUTES lpEventAttributes, IN BOOL bManualReset, IN BOOL bInitialState, IN LPCSTR lpName);
где
lpEventAttributes – указатель наследования возвращенного дескриптора дочерними процессами и указатель используемого дескриптора защиты; по умолчанию NULL (дескриптор не наследуется, используется дескриптор защиты по умолчанию);
bManualReset определяет тип: true – с ручным сбросом, false – с автоматическим.
bInitialState задает начальное состояние: true – сигнальное, false – несигнальное.
1
lpName – это имя события, которое позволяет обращаться к событию из потоков разных процессов. Имя может быть не задано, тогда создается событие без имени. Событие без имени можно использовать только в рамках одного процесса.
При успешном выполнении функция CreateEvent возвращает дескриптор события, иначе NULL. Узнать после вызова CreateEvent, существовало ли уже такое событие, можно оператором:
if(GetLastError() == ERROR_ALREADY_EXISTS)
Функции BOOL SetEvent(HANDLE hEvent) и BOOL ResetEvent(HANDLE hEvent); устанавливают событие в сигнальное и несигнальное состояния соответственно.
Управлять состоянием события можно также функцией BOOL PulseEvent(HANDLE hEvent), которая эквивалентна последовательному выполнению функций SetEvent и ResetEvent. В случае события с ручным сбросом это позволит всем потокам, которые ждут событие и могут немедленно завершить ожидание (например ждут только это событие), выйти из ожидания. Затем функция PulseEvent переводит событие в несигнальное состояние и завершает работу. Для событий с автоматическим сбросом функция позволит одному из потоков выйти из ожидания (если это возможно), после чего переводит событие в несигнальное состояние и завершает работу.
Функция HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCSTR lpName) открывает существующий объект события с именем lpName и возвращает его дескриптор.
Параметр dwDesiredAccess может принимать значения:
EVENT_ALL_ACCESS: поток может выполнять любые действия над событием;
EVENT_MODIFY_STATE: поток может использовать функции SetEvent и ResetEvent;
SYNCHRONIZE: поток может использовать событие в функциях ожидания. Параметр bInheritHandle определяет, наследуется ли возвращаемый дескриптор
дочерними процессами. Значение true означает наследование.
Функция OpenEvent возвращает дескриптор только в случае, если объект события уже был создан каким-то процессом. В противном случае возвращается NULL.
Пример. Поток 1 готовит данные для обработки независимыми (параллельно работающими) потоками 2 и 3. В потоке 1 размещаем код:
HANDLE E[2];
E[0] = CreateEvent(NULL, true, false, "MyEvent2");
E[1] = CreateEvent(NULL, true, false, "MyEvent3");
HANDLE H = CreateEvent(NULL, true, false, "MyEvent1");
//подготовка данных
...
//установка события MyEvent1 SetEvent(H);
//ожидание событий MyEvent2 и MyEvent 3 WaitForMultipleObjects(2, E, true, INFINITE); ResetEvent(E[0]);
ResetEvent(E[1]);
ResetEvent(H);
2
// продолжение выполнения после установки событий MyEvent2 и MyEvent3
С помощью события "MyEvent1" поток 1 будет оповещать потоки 2 и 3 об окончании подготовки данных. События "MyEvent2" и "MyEvent3" будут использоваться для получения информации от потоков 2 и 3. После завершения подготовки данных событие "MyEvent1" с помощью функции SetEvent переводится в сигнальное состояние, которое будет опознано потоками 2 и 3. Затем вызывается функция WaitForMultipleObjects, обеспечивающая ожидание до тех пор, пока оба события "MyEvent2" и "MyEvent3" не окажутся в сигнальном состоянии. После этого работа потока 1 будет продолжена. Перед продолжением работы все события сбрасываются в несигнальное состояние функцией ResetEvent. Такой сброс необходим, если приведённый код выполняется больше одного раза. Поскольку окончание ожидания наступает при сигнальном состоянии событий "MyEvent2" и "MyEvent3", то оно сохранится, и при повторном обращении к функции WaitForMultipleObjects ожидание не состоится. В потоке 2, который должен среагировать на событие "MyEvent1", выполнить свои операции над подготовленными данными и оповестить о завершении выполнения поток 1 через событие "MyEvent2", размещаем код:
HANDLE H = CreateEvent(NULL, true, false, "MyEvent1")
WaitForSingleObject(H, INFINITE);
//начало обработки данных, подготовленных потоком 1
...
//сообщение об окончании обработки SetEvent(OpenEvent(EVENT_ALL_ACCESS, true, "MyEvent2"));
Код начинается с создания дескриптора Н события "MyEvent1" и ожидания того, когда это событие будет переведено первым приложением в сигнальное состояние. Когда это происходит, начинается обработка полученных данных. После этого функцией OpenEvent открывается дескриптор события "MyEvent2" и функцией SetEvent событие переводится в сигнальное состояние.
В потоке 3, который должен среагировать на событие "MyEvent1", выполнить свои операции над подготовленными данными и оповестить о завершении выполнения поток 1 через событие "MyEvent3", размещаем аналогичный код, заменив в нем имя события "MyEvent2" на MyEvent3".
3