Windows для профессионалов

       

самая примитивная разновидность объектов ядра.


События - самая примитивная разновидность объектов ядра. Они содержат счетчик числа пользователей (как и все объекты ядра) и две булевы переменные: одна сооб щает тип данного объекта-события, другая — его состояние (свободен или занят).
События просто уведомляют об окончании какой-либо операции. Объекты-собы тия бывают двух типов: со сбросом вручную (manual-reset events) и с автосбросом (auto-reset events). Первые позволяют возобновлять выполнение сразу нескольких ждущих потоков, вторые — только одного.
Объекты-события обычно используют в том случае, когда какой-то поток выпол няет инициализацию, а затем сигнализирует другому потоку, что тот может продол жить работу. Инициализирующий поток переводит объект "событие" в занятое состо яние и приступает к своим операциям. Закончив, он сбрасывает событие в свободное состояние. Тогда другой поток, который ждал перехода события в свободное состоя ние, пробуждается и вновь становится планируемым.
Объект ядра "событие" создается функцией CreateEvent:
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa, BOOL fManualReset, BOOL fInitialState, PCTSTR pszName);
В главе 3 мы обсуждали общие концепции, связанные с объектами ядра, — защи ту, учет числа пользователей объектов, наследование их описателей и совместное использование объектов за счет присвоения им одинаковых имен. Поскольку всс это Вы теперь знаете, я не буду рассматривать первый и последний параметры данной функции.
Пареметр fManualReset (булева переменная) сообщает системе, хотите Вы создать событие со сбросом вручную (TRUE) или с автосбросом (FALSE). Параметру fInitialState определяет начальное состояние события — свободное (TRUE) или занятое (FALSE). После того как система создает объект событие, CreateEvent возвращает описатель события, специфичный для конкретного процесса. Потоки из других процессов мо гут получить доступ к этому объекту: 1) вызовом CreateEvent с тем же параметром pszName;, 2) наследованием описателя; 3) применением функции DuplicateHandle;, и 4) вызовом OpenEvent c передачей в параметре pszName имени, совпадающего с ука занным в аналогичном параметре функции CreateEvent. Вот что представляет собой функция OpenEvent.


HANDLE OpenEvent( DWORD fdwAccess, BOOL fInhent, PCTSTR pszName);
Ненужный объект ядра "событие" следует, как всегда, закрыть вызовом CloseHandle Создав собьпис, Вы можете напрямую управлять его состоянием. Чтобы перевес ти его в свободное состояние, Вы вызываете:
BOOL SetEvenT(HANDLE hEvenеt);
А чтобы поменять его на занятое
BOOL ResetEvent(HANDLE hEvent);


Вот так все просто
Для событий с автосбросом действует следующее правило Когда его ожидание потоком успешно завершается, этот объект автоматически сбрасывается в занятое состояние. Отсюда и произошло название таких объектов-событий Для этого объек та обычно не требуется вызывать ResetEvent, поскольку система сама восстанавливает его состояние А для событий со сбросом вручную никаких побочных эффектов ус пешного ожидания не предусмотрено.
Рассмотрим небольшой пример тому, как на практике использовать объекты ядра "событие" для синхронизации потоков Начнем с такого кода.
// глобальный описатель события со сбросом вручную (в занятом состоянии)
HANDLE g_hEvent;
int WINAPI WinMain( )
{
// создаем объект "событие со сбросом вручную (в занятом состоянии)
g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
// порождаем три новых потока
HANDLE hThread[3];
DWORD dwThreadTD;
hThread[0] = _beginthreadex(NULL, 0, WordCount, NULL, 0, &dwThreadlD);
hThread[1] = _beginthreadex(NULL, 0, SpellCheck, NULL, 0, &dwThreadID);
hTbread[2] = _beginthreadex(NULL, 0, GrarrmarCheck, NULL, 0, &dwThreadID);
OpenFileAndReadContentsIntoMemory( );
// разрешаем всем грем потокам обращаться к памяти
SetEvent(g__hEvent),
}
DWORD WINAPI WordCount(PVOID pvParam)
{
// ждем, когда в память будут загружены данные из файла
WaitForSingleObject(g_hEvent, iNeiNITE);
// обращаемся к блоку памяти
return(0);
}
DWORD WINAPI SpellCheck(PVOID pvParam)
{
// ждем, когда в память будут загружены данные из файла
WaitForSingleObject(g_hFvent, INFINITE);
// обращаемся к блоку пэмяти
return(0};
}
DWORD WINAPI GrammarCheck(PVOID pvParam)


{
// ждем, когда в память будут загружены данные из файла
WaitForSingleObject(g_hFvent, INFINITE);
// обращаемся к блоку памяти
return(0);
}
При запуске этот процесс создает занятое событие со сбросом вручную и запи сывает его описатель в глобальную переменную. Это упрощает другим потокам про цесса доступ к тому жс объекту-событию Затем порождается три потока. Они ждут, когда в память будут загружены данные (текст) из некоего файла, и потом обращают ся к этим данным, один поток подсчитывает количество слов, другой проверяет ор фографические ошибки, третий — грамматические Все три функции потоков начи нают работать одинаково каждый поток вызывает WaitForSingleObject, которая при останавливает его до гех пор, пока первичный поток не считает в память содержи мое файла
Загрузив нужные данные, первичный поток вызывает SetEvent, которая переводит событие в свободное состояние. В этот момент система пробуждает три вторичных потока, и они, вновь получив процессорное время, обращаются к блоку памяти За метьте, что они получают доступ к памяти в режиме только для чтения. Это единствен ная причина, по которой все три потока могут выполняться одновременно
Если событие со сбросом вручную заменить на событие с автосбросом, програм ма будет вести себя совершенно иначе После вызова первичным потоком функции SetEvent система возобновит выполнение только одного из вторичных потоков. Ка кого именно — сказать заранее нельзя. Остальные два потока продолжат ждать.
Поток, вновь ставший планируемым, получает монопольный доступ к блоку па мяги, где хранятся данные, считанные из файла Давайте перепишем функции пото ков так, чтобы перед самым возвратом управления они (подобно функции WinMain) вызывали SetFvent Теперь функции потоков выглядят следующим образом:
DWORD WINAPI WordCount(PVOID pvParam)
{
// ждем, когда в память будут загружены данные из файла
WaitForSingleObject(a_hEvent, INFINITE);
// обращаемся к блоку памяти
SetEvent(g_hEvent);
return(0);
}
DWORD WINAPI SpellCneck(PVOID pvParam)


{
// ждем, когда в память будут загружены данные из файла
WaitForSingleObject(g htvent, INFINITE);
// обращаемся к блоку памяти
SetEvent(g_hEvent);
return(0);
}
DWORD WINAPI GrammarCheck(PVOID pvParam)
{
// ждем, когда в память будут загружены данные из файла
WaitForSingleObject(g_hEvent, INFINITF);
// обращаемся к блоку памяти
SetEvent(g_hEvent);
return(0);
}
Закончив свою работу с данными, поток вызывает SetEvent, которая разрешает системе возобновить выполнение следующего из двух ждущих потоков. И опять мы не знаем, какой поток выберет система, но так или иначе кто-то из них получит мо нопольный доступ к тому же блоку памяти. Когда и этот поток закончит свою работу, он тоже вызовет SetEvent, после чего с блоком памяти сможет монопольно опериро вать третий, последний поток Обратите внимание, что использование события с ав тосбросом снимает проблему с доступом вторичных потоков к памяти как для чте ния, так и для записи; Вам больше не нужно ограничивать их доступ только чтением. Этот пример четко иллюстрирует различия в применении событий со сбросом вруч ную и с автосбросом.
Для полноты картины упомяну о еще одной функции, которую можно использо вать с объектами-событиями
BOOL PulseEvent(HANDLE hEvent);
PuteeEvent освобождает событие и тут жс переводит его обратно в занятое состо яние; ее вызов равнозначен последовательному вызову SelEvent и ResetEvent. Если Вы вызываете PulseEvent для события со сбросом вручную, любые потоки, ждущие этот объект; становятся планируемыми. При вызове этой функции применительно к со бытию с автосбросом пробуждается только одип из ждущих потоков. А если ни один из потоков не ждет объект-событие, вызов функции не дает никакого эффекта
Особой пользы от PulseEuent я не вижу В сущности, я никогда не пользовался ею на практике, потому что абсолютно неясно, какой из потоков заметит этот импульс и станет планируемым Наверное, в каких-то сценариях PulseEuent можст пригодиться, но ничего такого мне в голову не приходит Когда мы перейдем к рассмотрению фун кции SignalObjectAndWait, я расскажу о PulseEvent чуть подробнее.

Содержание раздела