Алгоритм для синхронизации между функциями WaitForSingleObject () и SetEvent ()? - PullRequest
1 голос
/ 05 марта 2012

Я хочу реализовать очередь сообщений для 2 потоков.Поток # 1 выведет сообщения в очередь и обработает их.Поток # 2 будет помещать сообщения в очередь.

Вот мой код:

 Thread #1 //Pop message and process
 {
    while(true)
    {
        Lock(mutex);
        message = messageQueue.Pop();
        Unlock(mutex);

        if (message == NULL) //the queue is empty
        {
           //assume that the interruption occurs here (*)
            WaitForSingleObject(hWakeUpEvent, INFINITE);
            continue;
        }
        else
        {
            //process message
        }
    }
}

Thread #2 //push  new message in queue and wake up thread #1
{
    Lock(mutex);
    messageQueue.Push(newMessage)
    Unlock(mutex);

    SetEvent(hWakeUpEvent);
}

Проблема в том, что в некоторых случаях SetEvent(hWakeUpEvent) будет вызываться раньше WaitForSingleObject() (примечание (*)), это будет опасно.

Ответы [ 3 ]

3 голосов
/ 06 марта 2012

Ваш код в порядке!

Нет реальной проблемы с синхронизацией между SetEvent и WaitForSingleObject: ключевая проблема заключается в том, что WaitForSingleObject для события будет проверять состояние события и ждать, пока оно не будет запущено.Если событие уже запущено, оно немедленно вернется.(С технической точки зрения, он запускается по уровню, а не по фронту.) Это означает, что хорошо, если SetEvent вызывается либо до, либо во время вызова WaitForSingleObject;WaitForSingleObject будет возвращаться в любом случае;или сразу, или когда SetEvent вызывается позже.

(Кстати, я предполагаю использовать событие автоматического сброса здесь. Я не могу придумать вескую причину для использования события ручного сброса; вы простов конечном итоге придется вызывать ResetEvent сразу после возвращения WaitForSingleObject, и существует опасность, что, если вы забудете об этом, вы можете в конечном итоге ожидать события, которого вы уже ждали, но забыли очистить. Кроме того, важно выполнить сброс до проверки базовойсостояние данных, в противном случае, если SetEvent вызывается между обработкой данных и вызовом Reset (), вы теряете эту информацию. Используйте функцию автоматического сброса и избегайте всего этого.)

-

[Редактировать: я неправильно прочитал код OP, выполняя по одному «pop» при каждом пробуждении, а не только при ожидании пустого, поэтому комментарии ниже относятся к коду этого сценария.Код OP на самом деле эквивалентен второму предложенному исправлению ниже.Таким образом, приведенный ниже текст на самом деле описывает довольно распространенную ошибку кодирования, когда события используются, поскольку они были семафорами, а не фактическим кодом OP.]

Но здесь есть другая проблема [или, еслибыл только один всплеск за ожидание ...], и это то, что объекты Win32 Events имеют только два состояния: не сигнализированное и сигнальное, поэтому вы можете использовать их только для отслеживания двоичного состояния, но не для подсчета.Если вы установили событие SetEvent и событие, которое уже было сигнализировано, оно остается сигнальным, и информация об этом дополнительном вызове SetEvent теряется.

В этом случае может произойти следующее:

  • Элементдобавлено, SetEvent вызвано, событие теперь сигнализируется.
  • Добавлен еще один элемент, SetEvent вызывается снова, событие остается сигнальным.
  • Рабочий поток вызывает WaitForSingleObject, который возвращает, очищая событие,
  • обрабатывается только один элемент,
  • рабочий поток вызывает WaitForsingleObject, который блокируется, поскольку событие не сигнализируется, даже если в очереди все еще есть элемент.

Есть дваспособы обойти это: классический способ Comp.Sci - использовать семафор вместо события - семафоры - это, по сути, события, которые подсчитывают все вызовы 'Set';наоборот, вы можете думать о событии как о семафоре с максимальным счетом 1, который игнорирует любые другие сигналы, кроме этого.

Альтернативный способ - продолжать использовать события, но когда рабочий поток просыпается, он можеттолько предполагайте, что в очереди может быть несколько элементов, и он должен попытаться обработать их все, прежде чем он вернется к ожиданию - обычно, помещая код, который выводит элемент в цикл, который извлекает элементы и обрабатывает ихпока его пусто.Событие теперь используется не для подсчета, а для сигнализации "очередь больше не пуста".(Обратите внимание, что когда вы делаете это, вы также можете получить случаи, когда при обработке очереди вы также обрабатываете элемент, который был только что добавлен и для которого был вызван SetEvent, так что когда рабочий поток достигает WaitForSingleObject, поток просыпается, нообнаруживает, что очередь пуста, так как элемент уже обработан, поначалу это может показаться немного удивительным, но на самом деле это нормально.)

Я считаю эти два в основном эквивалентными;У обоих есть свои плюсы и минусы, но они оба верны.(Лично я предпочитаю подход, основанный на событиях, поскольку он отделяет понятие «что-то, что нужно сделать» или «доступно больше данных» от количества этой работы или данных.)

2 голосов
/ 05 марта 2012

«Классический» способ (т. Е. Наверняка будет работать правильно) - это использование семафора (см. CreateSemaphore, ReleaseSemaphore API).Создать семафор пустой.В потоке продюсера заблокируйте мьютекс, нажмите сообщение, разблокируйте мьютекс, освободите юнит для семафора.В потоке потребителя дождитесь дескриптора семафора с помощью WFSO (как вы ожидаете события выше), затем заблокируйте мьютекс, выведите сообщение, разблокируйте мьютекс.

Почему это лучше, чем события?

1) Нет необходимости проверять счетчик очереди - семафор подсчитывает сообщения.

2) Сигнал семафору не «теряется» только потому, что ни один поток не ожидает его.

3) Не проверка количества очередей означает, что в результате и пути кода, полученного в результате, такая проверка не может быть неправильной из-за вытеснения.

4) Она будет работать для нескольких производителей и несколькихпотребители без изменений.

5) Он более кросс-платформенный - все приоритетные ОС имеют мьютексы / семафоры.

0 голосов
/ 05 марта 2012

Было бы опасно, если бы несколько потоков потребляли данные одновременно или если вы использовали PulseEvent вместо SetEvent.

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

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