AutoResetEvent - два набора вызовов быстро не гарантируют освобождение потока - почему? - PullRequest
2 голосов
/ 29 февраля 2012

Я читал документацию 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. это действительное предупреждение?

Ответы [ 2 ]

2 голосов
/ 29 февраля 2012

Сообщение на MSDN действительно является действительным сообщением. То, что происходит внутри, выглядит примерно так:

  1. Тема A ожидает события
  2. Поток B устанавливает событие
  3. [Если нить A находится в спин-блокировке]
    1. [да] Thread a обнаруживает, что событие установлено, сбрасывает его и возобновляет работу
    2. [нет] Событие сообщит, что поток A проснется, после пробуждения поток A сбросит событие, чтобы возобновить свою работу.

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

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

2 голосов
/ 29 февраля 2012

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

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

...