/* Уменьшить значение счетчика семафора на 2. */
WaitForSingleObject(hSem, INFINITE);
WaitForSingleObject(hSem, INFINITE);
…
/* Увеличить значение счетчика семафора на 2. */
ReleaseSemaphore(hSem, 2, &PrevCount);
Чтобы увидеть, каким образом в подобной ситуации может возникнуть взаимоблокировка, предположим, что максимальное и начальное значения счетчика устанавливаются равными 2 и что первый из двух потоков завершает первый цикл ожидания, а затем вытесняется. Далее второй поток может завершить первый цикл ожидания и уменьшить значение счетчика до 0. Оба потока окажутся блокированными на неопределенное время, поскольку ни одна из них не сможет выполнить второй цикл ожидания. Такая простая ситуация взаимоблокировки является довольно типичной.
Один из возможных вариантов правильного решения заключается в том, чтобы защитить циклы ожидания при помощи мьютекса или объекта CRITI-CAL_SECTION, как показано в приведенном ниже фрагменте программного кода:
/* Уменьшаем значение счетчика семафора на 2. */
EnterCriticalSection(&csSem);
WaitForSingleObject(hSem, INFINITE);
WaitForSingleObject(hSem, INFINITE);
LeaveCriticalSection (&csSem);
…
ReleaseSemaphore(hSem, 2, &PrevCount);
Но и эта реализация, в таком общем виде, страдает ограничениями. Предположим, например, что в счетчике семафора остается две единицы, и потоку А необходимы три единицы, а потоку В — только две. Если первой начнет выполняться поток А, то он выполнит два цикла ожидания и блокируется на третьем, продолжая владеть мьютексом. При этом поток В, которому были необходимы только две единицы, по-прежнему будет оставаться блокированным.
Казалось бы, можно воспользоваться функцией WaitForMultipleObjects с использованием одного и того же дескриптора семафора в нескольких элементах массива дескрипторов. Однако такое предложение было бы неудачным по двум причинам. Прежде всего, обнаружив, что два дескриптора указывают на один и тот же объект, функция WaitForMultipleObjects завершится с ошибкой. Более того, даже если значение счетчика семафора будет составлять только 1, сигнализироваться будут все дескрипторы, что противоречит самой исходной цели.
Полное решение проблемы множественных циклов ожидания предлагается в упражнении 10.11.
Проектировать семафоры Windows было бы гораздо удобнее, если бы существовала возможность выполнять множественные циклы ожидания в виде одной атомарной операции (atomic multiple-wait operation).
События
Последним из рассматриваемых нами типов объектов синхронизации ядра являются
Важной дополнительной возможностью, обеспечиваемой объектами событий, является то, что переход в сигнальное состояние единственного объекта события способен вывести из состояния ожидания одновременно несколько потоков. Объекты события делятся на сбрасываемые вручную и автоматически сбрасываемые, и это их свойство устанавливается при вызове функции CreateEvent.
• Сбрасываемые вручную события (manual-reset events) могут сигнализировать одновременно всем потокам, ожидающим наступления этого события, и переводятся в несигнальное состояние программно.
• Автоматически сбрасываемые события (auto-reset event) сбрасываются самостоятельно после освобождения одного из ожидающих потоков, тогда как другие ожидающие потоки продолжают ожидать перехода события в сигнальное состояние.
События используют пять новых функций: CreateEvent, OpenEvent, SetEvent, ResetEvent и CreateEvent.
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpsa, BOOL bManualReset, BOOL bInitialState, LPTCSTR lpEventName)
Чтобы создать событие, сбрасываемое вручную, необходимо установить значение параметра bManualReset равным True. Точно так же, чтобы сделать начальное состояние события сигнальным, установите равным True значение параметра bInitialState. Для открытия именованного объекта события используется функция OpenEvent, причем это может сделать и другой процесс.
Для управления объектами событий используются следующие три функции:
BOOL SetEvent(HANDLE hEvent)
BOOL ResetEvent(HANDLE hEvent)
BOOL PulseEvent(HANDLE hEvent)