AutoResetEvent - два набора вызовов быстро не гарантируют выпуск потока - почему?

Я читал документацию AutoResetEvent в MSDN, и следующее предупреждение меня беспокоит.

«Важно: нет гарантии, что каждый вызов метода Set освободит поток. Если два вызова слишком близки друг к другу, так что второй вызов происходит до того, как поток будет освобожден, освобождается только один поток. второй вызов не произошел. Кроме того, если Set вызывается, когда нет ожидающих потоков, а AutoResetEvent уже сигнализируется, вызов не имеет никакого эффекта».

Но это предупреждение в основном убивает саму причину использования таких методов синхронизации потоков. Например, у меня есть список, который будет содержать рабочие места. И есть только один производитель, который добавит рабочие места в список. У меня есть потребители (более одного), ожидающие получения задания из списка.. что-то вроде этого..

Продюсер:

void AddJob(Job j)
{
    lock(qLock)
    {
        jobQ.Enqueue(j);
    }

    newJobEvent.Set(); // newJobEvent is AutoResetEvent
}

Потребитель

void Run()
{
    while(canRun)
    {
        newJobEvent.WaitOne();

        IJob job = null;

        lock(qLock)
        {
            job = jobQ.Dequeue();
        }

        // process job
    }
}

Если приведенное выше предупреждение верно, то, если я очень быстро поставлю в очередь два задания, только один поток подхватит задание, не так ли? Я исходил из того, что Set будет атомарным, то есть он делает следующее:

  1. Установить событие
  2. Если потоки ожидают, выберите один поток для пробуждения
  3. сбросить событие
  4. запустить выбранный поток.

Так что я в основном запутался в предупреждении в MSDN. это действительное предупреждение?


person cgcoder    schedule 29.02.2012    source источник
comment
Да, этот код сработает правильно только случайно. Не изобретайте это колесо, погуглите «очередь производителей-потребителей С#» для множества примеров.   -  person Hans Passant    schedule 29.02.2012
comment
большинство производителей-потребителей, которых я нашел при поиске, используют подход ожидания/сигнала. Проблема с этим подходом в том, что когда я хочу, чтобы рабочий уволился, мне приходится предлагать фиктивные задания. Но если я могу использовать события, я могу заставить работника ждать два события. Один для работы, а другой для выключения. Так чище..   -  person cgcoder    schedule 29.02.2012


Ответы (2)


Даже если предупреждение неверно, а Set является атомарным, зачем использовать здесь AutoResetEvent? Допустим, у вас есть несколько производителей, которые ставят в очередь 3 события подряд, и есть один потребитель. После обработки второго задания потребитель блокирует и никогда не обрабатывает третье.

Я бы использовал ReaderWriterLockSlim для этого типа синхронизации. По сути, вам нужно, чтобы несколько производителей могли иметь блокировки записи, но вы не хотите, чтобы потребители блокировали производителей в течение длительного времени, пока они только читают размер очереди.

person Mike Zboray    schedule 29.02.2012

Сообщение в MSDN действительно является допустимым сообщением. Внутри происходит что-то вроде этого:

  1. Поток A ожидает события
  2. Поток B устанавливает событие
  3. [If thread A is in spinlock]
    1. [yes] Thread a detects that the event is set, unsets it and resumes its work
    2. [no] Событие сообщит потоку A о необходимости пробуждения, после пробуждения поток A отключит событие и возобновит свою работу.

Обратите внимание, что внутренняя логика не является синхронной, поскольку поток B не ждет, пока поток A продолжит свою работу. Вы можете сделать это синхронным, введя временное событие ManualResetEvent, которое поток A должен сигнализировать, как только он продолжит свою работу, и какой поток B должен ожидать. Это не делается по умолчанию из-за внутренней работы модели многопоточности Windows. Я предполагаю, что документация вводит в заблуждение, но правильно говорит, что метод Set освобождает только один или несколько ожидающих потоков.

В качестве альтернативы я бы посоветовал вам взглянуть на класс BlockingCollection в пространстве имен System.Collections.Concurrent BCL, представленном в .NET 4.0, который делает именно то, что вы пытаетесь сделать.

person Polity    schedule 29.02.2012