Потоки, ожидающие события, не всегда ловят сигнал о событии - PullRequest
5 голосов
/ 07 мая 2011

У меня есть приложение, в котором несколько потоков ожидают сигнала от одного и того же объекта события.Проблема, которую я вижу, является типом состояния гонки в том, что иногда состояния ожидания некоторых потоков (WaitForMultipleObjects) возвращаются в результате сигнала события, а состояния ожидания других потоков, по-видимому, не видят сигнал события, потому что онине возвращайсяЭти события были созданы с использованием CreateEvent в качестве объектов событий с ручным сбросом.

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

switch ( dwObjectWaitState = ::WaitForMultipleObjects( i, pHandles, FALSE, INFINITE ) )
{
case WAIT_OBJECT_0 + BAS_MESSAGE_READY_EVT_ID:
    ::ResetEvent( pHandles[BAS_MESSAGE_READY_EVT_ID] );
    /* handles the event */
    break;
}

Другими словами, проблема, которую я вижу, заключается в том, что описано в разделе Замечаниядля PulseEvent на веб-сайте MSDN :

Если вызов PulseEvent происходит в то время, когда поток был удален из состояния ожидания, поток не будет освобожден, поскольку PulseEvent выпускаеттолько те темы, которые ждут в данный момент, это называется.Поэтому PulseEvent ненадежен и не должен использоваться новыми приложениями.Вместо этого используйте условные переменные.

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

Есть ли лучший способ сделать это?Спасибо.

Ответы [ 3 ]

3 голосов
/ 07 мая 2011

Да, есть лучший способ:

[...] Вместо этого используйте условные переменные.

http://msdn.microsoft.com/en-us/library/ms682052(v=vs.85).aspx

ИщитеWakeAllConditionVariable конкретно

2 голосов
/ 07 мая 2011

Используйте условную переменную, как в описании PulseEvent. Единственная проблема заключается в том, что встроенная условная переменная в Windows была реализована начиная с Vista, поэтому в более старой системе, такой как XP, ее нет. Но вы можете эмулировать условную переменную, используя некоторые другие объекты синхронизации (http://www1.cse.wustl.edu/~schmidt/win32-cv-1.html), но я думаю, что самый простой способ - использовать условную переменную из библиотеки boost и ее метод notify_all для пробуждения всех потоков (http://www.boost.org/doc/libs/1_41_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref)

Другая возможность (но не очень красивая) состоит в том, чтобы создать одно событие для каждого потока, и когда прямо сейчас у вас есть PulseEvent, вы можете вызвать SetEvent для всех из них. Для этого решения, вероятно, события автосброса будут работать лучше.

1 голос
/ 11 мая 2017

Почему PulseEvent () ненадежен и что делать без него

Событие автосброса имеет значение king!

PulseEvent появился только в Windows NT 4.0.Он не существовал в оригинальной Windows NT 3.1.Напротив, надежные функции, такие как CreateEvent, SetEvent и WaitForMultipleObjects, существовали с момента запуска Windows NT, поэтому рассмотрите возможность их использования.

Функция CreateEvent имеет аргумент bManualReset.Если этот параметр имеет значение TRUE, функция создает объект события с ручным сбросом, для чего требуется использовать функцию ResetEvent, чтобы установить состояние события как не сигнализированное.Это не то, что вам нужно.Если этот параметр имеет значение FALSE, функция создает объект события с автосбросом, и система автоматически сбрасывает состояние события как не сигнализированное после освобождения одного ожидающего потока.

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

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

Поэтому создайте события следующим образом:

EventHandle := CreateEvent(nil, FALSE, FALSE, nil);

Дождаться события из одного потока и выполнить SetEvent из другого потока.Это очень просто и очень надежно.

Никогда не вызывайте ResetEvent (так как он автоматически сбрасывается) или PulseEvent (так как это ненадежно и не рекомендуется).Даже Microsoft признала, что PulseEvent не следует использовать.См. https://msdn.microsoft.com/en-us/library/windows/desktop/ms684914(v=vs.85).aspx

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

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

Мы никогда не использовали PulseEvent в наших приложениях.Что касается событий автоматического сброса, мы используем их начиная с Windows NT 3.51, и они работают очень хорошо.

Что делать, когда несколько потоков ожидают один объект

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

Вы написали, что «единственное решение, которое я вижу, состоит в том, чтобы каждый поток регистрировал использование данного объекта события в потоке владельца этого объекта».Это правильно.

Вы также написали, что «поток владельца может определить, когда безопасно сбросить состояние сигнала объекта события» - это нецелесообразно и небезопасно.Лучше всего использовать события автоматического сброса, чтобы они автоматически сбрасывались.

Итак, вам нужно иметь столько событий, сколько и потоков.Кроме того, вам нужно будет вести список зарегистрированных тем.Таким образом, чтобы уведомить все потоки, вам придется выполнить SetEvent в цикле для всех дескрипторов событий.Это очень быстрый, надежный и дешевый способ.События намного дешевле, чем темы.Таким образом, количество потоков является проблемой, а не количество событий.Практически нет ограничений на объекты ядра - ограничение для каждого процесса для дескрипторов ядра составляет 2 ^ 24.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...