Функция PulseEvent освобождает один поток (или все потоки, если вы используете событие с ручным сбросом), который ждал событие, после чего возвращает событие в сброшенное (unset) состояние. Если ни один поток не ждал событие, то ничего не происходит.
И в этом-то и есть проблема.
Как вы узнаете, ждёт ли поток, на самом деле, событие, как вы думаете? Конечно же, вы не можете сделать так:
SignalSemaphore(hOtherSemaphore); WaitForSingleObject(hEvent, INFINITE);потому что в этом примере есть гонка (race) между установкой семафора и ожиданием. Поток, который пробуждается семафором, может выполнить всю свою работу и вызвать PulseEvent на событие прежде, чем этот код дойдёт до WaitForSingleObject.
Вы можете использовать функцию SignalObjectAndWait, которая комбинирует сигнализирование и ожидание в атомарную операцию. Но даже тогда вы не можете гарантировать, что в заданный момент времени поток будет ждать события.
Дело в том, что пока поток ждёт события, драйвер устройства или даже часть самого ядра могут "угнать" (borrow) поток для выполнения какой-нибудь задачи (посредством "kernel-mode APC"). В течение этого времени поток не находится в режиме ожидания (поскольку он занят выполнением работы драйвера устройства). И если вызов PulseEvent произойдёт в тот момент, когда поток "позаимствовали", то он не проснётся, поскольку функция PulseEvent пробуждает только потоки, которые ждали в момент вызова функции.
Дело даже не в том, что вы (как прикладная программа) не можете запретить ядру брать ваш поток, а в том, что вы вообще не можете определить, что это случилось.
(Одним из мест, где вы, вероятно, увидите это в действии, является подключение отладчика к процессу, поскольку отладчик делает такие вещи как приостановка и возобновление потоков, которые приводят к выполнению APC ядра).
В результате - функция PulseEvent является бесполезной и её следует избегать. Она существует исключительно ради обратной совместимости.
Примечание: эти все вещи с APC ядра также означают, что вы не можете предсказать, какой поток будет разбужен, когда вы включаете семафор, событие с авто-сбросом или любой другой объект синхронизации, который в сигнальном состоянии освобождает только один поток. Если поток "позаимствован" для обслуживания APC ядра, то когда он возвращается в очередь ожидания, он становится в её конец. Соответственно, порядок объектов, ожидающих объект ядра, непредсказуем, и вы не должны делать никаких предположений о нём.
В самом деле, при отладке не всегда объект получает уведомление от PulseEvent().
ОтветитьУдалить