Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lab7.doc
Скачиваний:
4
Добавлен:
12.11.2019
Размер:
287 Кб
Скачать
☆

13.1. Побочные эффекты успешного ожидания

Успешный вызов WaitForSingleObject или WaitForMultipleObjecls на самом деле меняет состояние некоторых объектов ядра. Под успешным вызовом я имею в виду тот, при котором функция видит, что объект освободился, и возвращает значение, относительное WAITOBJECT_0. Вызов считается неудачным, если возвращается WAIT_TIMEOUT или WAIT_FAILED. В последнем случае состояние каких-либо объектов не меняется.

Изменение состояния объекта в результате вызова можно назвать побочным эффектом успешного ожидания (successful wait side effect). Например, поток ждет объект «событие с автосбросом» (auto-reset event objcct) (см.ниже). Когда объект переходит в свободное состояние, функция обнаруживает это и может вернуть вызывающему потоку значение WAIT_OBJECT_0. Однако перед самым возвратом из функции событие переводится в занятое состояние — здесь сказывается побочный эффект успешного ожидания.

Объекты ядра «событие с автосбросом» ведут себя подобным образом, потому что таково одно из правил, определенных Microsoft для объектов этого типа. Другие объекты дают иные побочные эффекты, а некоторые — вообще никаких. К последним относятся объекты ядра «процесс» и «поток», так что поток, ожидающий один из этих объектов, никогда не изменит его состояние. Подробнее о том, как ведут себя объекты ядра, я буду рассказывать при рассмотрении соответствующих объектов.

14. События

События - самая примитивная разновидность объектов ядра. Они содержат счетчик числа пользователей (как и все объекты ядра) и две булевы переменные: одна сообщает тип данного объекта-события, другая — его состояние (свободен или занят).

События просто уведомляют об окончании какой-либо операции. Объекты-события бывают двух типов: со сбросом вручную (manual-reset events) и с автосбросом (auto-reset events). Первые позволяют возобновлять выполнение сразу нескольких ждущих потоков, вторые — только одного.

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

Объект ядра «событие» создается функцией CreateEvent:

HANDLE CreateEvent( PSECURITY_ATTRIBUTES psa, BOOL fManualReset, BOOL fInitialState, PCTSTR pszName);

Параметр fManualReset (булева переменная) сообщает системе, создать событие со сбросом вручную (TRUE) или с автосбросом (FALSE).

Параметру fInitialState определяет начальное состояние события — свободное (TRUE) или занятое (FALSE). После того как система создает объект событие, CreateEvent возвращает описатель события, специфичный для конкретного процесса. Потоки из других процессов могут получить доступ к этому объекту: 1) вызовом CreateEvent с тем же параметром pszName; 2) наследованием описателя; 3) применением функции DuplicateHandle; и 4) вызовом OpenEvent с передачей в параметре pszName имени, совпадающего с указанным в аналогичном параметре функции CreateEvent. Вот что представляет собой функция OpenEvent.

HANDLE OpenEvent( DWORD fdwAccess, BOOL fInhent, PCTSTR pszName);

Ненужный объект ядра «событие» следует, как всегда, закрыть вызовом CloseHandle. Создав событие, можно напрямую управлять его состоянием. Чтобы перевести его в свободное состояние, необходим вызов:

BOOL SetEvent(HANDLE hEvent);

А чтобы поменять его на занятое:

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, INFINITE); // обращаемся к блоку памяти return(0);

}

DWORD WINAPI SpellCheck(PVOID pvParam) {

// ждем, когда в память будут загружены данные из файла WaitForSingleObject(g_hEvent, INFINITE);

// обращаемся к блоку памяти

return(0}; }

DWORD WINAPI GrammarCheck(PVOID pvParam) {

// ждем, когда в память будут загружены данные из файла WaitForSingleObject(g_hEvent, INFINITE);

// обращаемся к блоку памяти

return(0); }

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

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

Если событие со сбросом вручную заменить на событие с автосбросом, программа будет вести себя совершенно иначе. После вызова первичным потоком функции SetEvent система возобновит выполнение только одного из вторичных потоков. Какого именно — сказать заранее нельзя. Остальные два потока продолжат ждать.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]