Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Синхронизация задач с помощью событий

Завершение работы задачи или процесса можно считать событием, которое произошло в системе, и в этом смысле только что расмотренные приемы синхронизации являются ничем иным, как ожиданием события. Однако синхронизация по завершению работы задачи далеко не всегда удобна, так как две активные задачи могут выполнять некоторые действия, требующие синхронизации во время их работы.

Операционная система Microsoft Windows NT позволяет создавать объекты синхронизации, которые называются событиями (event object). Эти объекты могут находиться в отмеченном или неотмеченном состоянии, причем установка состояния выполняется вызовом соответствующей функции.

Схема использования событий достаточно проста.

Одна из задач создает объект-событие, вызывая для этого функцию CreateEvent. При этом событие имеет имя, которое доступно всем задачам активных процессов. В процессе создания или позже эта задача устанавливает событие в исходное состояние (отмеченное или неотмеченное).

Вызывая функции WaitForSingleObject или WaitForMultipleObjects, задача может выполнять ожидание момента, когда событие перейдет в отмеченное состояние.

Другая задача, принадлежащая тому же самому или другому процессу, может получить идентификатор события по его имени, например, с помощью функции OpenEvent XE "OpenEvent" . Далее, пользуясь функциями SetEvent XE "SetEvent" , ResetEvent XE "ResetEvent" или PulseEvent, XE "PulseEvent" эта задача может изменить состояние события.

На рис. 4.2 приведен пример использования события для синхронизации двух задач, работающих одновременно.

Рис. 4.2. Пример использования события для синхронизации двух задач

Представим себе, что первая задача занимается отображением данных, которые готовятся второй задачей для отображения небольшими порциями (например, читаются с диска).

После создания неотмеченного события первая задача переходит в состояние ожидания, пока вторая задача не подготовит для нее данные. Как только это произойдет, вторая задача отмечает и затем сбрасывает событие, что приводит к завершению ожидания первой задачей.

Отобразив подготовленные данные, первая задача опять входит в состояние ожидания, пока вторая задача не подготовит данные и не отметит событие. Таким образом две задачи синхронизуют свою работу с помощью объекта-события.

Создание события

Для создания события задача должна вызвать функцию CreateEvent XE "CreateEvent" , прототип которой приведен ниже:


HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // атрибуты защиты 
  BOOL    bManualReset,  // флаг ручного сброса события 
  BOOL    bInitialState, // флаг начального состояния события 
  LPCTSTR lpName);       // адрес имени объекта-события 

Параметр lpEventAttributes задает атрибуты защиты и в большинстве случаев может быть указан как NULL.

С помощью параметра bManualReset вы можете выбрать один из двух режимов работы объекта-события: ручной или автоматический. Если значение этого параметра равно TRUE, событие нужно сбрасывать вручную при помощи функции ResetEvent XE "ResetEvent" , которую мы рассмотрим немного позже. Если же для параметра bManualReset указать значение FALSE, событие будет сброшено (то есть переведено в неотмеченное состояние) автоматически сразу после того как задача завершит ожидание этого события.

Параметр bInitialState определяет начальное состояние события. Если этот параметр равен TRUE, объект-событие создается в отмеченном состоянии, а если FALSE - в неотмеченном.

Для того чтобы событием могли пользоваться задачи, созданные разными процессами, необходимо с помощью параметра lpName задать имя объекта-события. В качестве имени вы можете выбрать любое имя размером не более MAX_PATH XE "MAX_PATH" символов, не содержащее символ “\”.

В том случае, когда событие используется задачами, работающими в рамках одного процесса, имя события можно не задавать, указав параметр lpName как NULL. При этом создается безымянное событие.

В случае успешного завершения функция CreateEvent возвращает идентификатор события, которым нужно будет пользоваться при выполнении всех операций над объектом-событием. При ошибке возвращается значение NULL (код ошибки можно получить при помощи функции GetLastError XE "GetLastError" ).

Возможна ситуация, когда при создании события вы указали имя уже существующего в системе события, созданного ранее другой задачей. В этом случае функция GetLastError XE "GetLastError" , вызванная сразу после вызова функции CreateEvent XE "CreateEvent" , возвращает значение ERROR_ALREADY_EXISTS XE "ERROR_ALREADY_EXISTS" .

Открытие события

Если событие используется задачами, созданными только в рамках одного процесса, его не нужно открывать. В качестве параметра функциям, изменяющим состояние объекта-события, вы можете передавать идентификатор события, полученный при его создании от функции CreateEvent XE "CreateEvent" .

Если же событие используется для синхронизации задач, принадлежащих разным процессам, вы должны при создании события задать его имя. Задача, изменяющая состояние события и принадлежащая другому процессу, должна открыть объект-событие с помощью функции OpenEvent XE "OpenEvent" , передав ей имя этого объекта.

Прототип функции OpenEvent представлен ниже:


HANDLE OpenEvent(
  DWORD   fdwAccess,      // флаги доступа 
  BOOL    fInherit,       // флаг наследования 
  LPCTSTR lpszEventName); // адрес имени объекта-события 

Флаги доступа, передаваемые через параметр fdwAccess, определяют требуемый уровень доступа к объекту-событию. Этот параметр может быть комбинацией следующих значений:

 Значение

 Описание

 EVENT_ALL_ACCESS

 Указаны все возможные флаги доступа

 EVENT_MODIFY_STATE

 Полученный идентификатор можно будет использовать для функций SetEvent и ResetEvent

 SYNCHRONIZE

 Полученный идентификатор можно будет использовать в любых функциях ожидания события

Параметр fInherit определяет возможность наследования полученного идентфикатора. Если этот параметр равен TRUE, идентфикатор может наследоваться дочерними процессами. Если же он равен FALSE, наследование не допускается.

И, наконец, через параметр lpszEventName вы должны передать функции адрес символьной строки, содержащей имя объекта-события.

Заметим, что с помощью функции OpenEvent несколько задач могут открыть один и тот же объект-событие и затем выполнять одновременное ожидание для этого объекта.

Установка события

Для установки объекта-события в отмеченное состояние используется функция SetEvent:


BOOL SetEvent(HANDLE hEvent);

В качестве единственного параметра этой функции необходимо передать идентификатор объекта-события, полученного от функции CreateEvent XE "CreateEvent" или OpenEvent XE "OpenEvent" . При успешном завершении возвращается значение TRUE, при ошибке - FALSE. В последнем случае можно получить код ошибки при помощи функции GetLastError XE "GetLastError" .

Cброс события

Сброс события (то есть установка его в неотмеченное состояние) выполняется функцией ResetEvent XE "ResetEvent" :


BOOL ResetEvent(HANDLE hEvent);

Если задача создала событие, работающее в автоматическом режиме, оно будет сбрасываться и без помощи этой функции, если только какая-либо задача выполняла ожидание этого события и событие произошло.

Функция PulseEvent

Функция PulseEvent выполняет установку объекта-события в отмеченное состояние с последующим сбросом события в неотмеченное состояние:


BOOL PulseEvent(HANDLE hEvent);

Если эта функция вызвана для события, работающего в ручном режиме, то все задачи, ожидающие это событие, завершат ожидание и продолжат свою работу. Событие при этом будет установлено в неотмеченное состояние.

Для автоматических объектов-событий выполняются аналогичные действия, однако функция возвращает управление сразу как только одна из ожидающих задач перейдет в активное состояние.

[Назад] [Содеожание] [Дальше]