Проблема многопоточности при проверке свойства Count - PullRequest
4 голосов
/ 20 октября 2010

У меня Список новых заданий .Некоторые потоки добавляют элементы в этот список, а другие - удаляют элементы из него, если он не пустой.У меня есть ManualResetEvent newJobEvent , который устанавливается при добавлении элементов в список и сбрасывается при удалении элементов из него:

Добавление элементов в список выполняется следующим образом:

lock(syncLock){
    newJobs.Add(job);
}
newJobEvent.Set();

Удаление заданий выполняется следующим образом:

if (newJobs.Count==0)
    newJobEvent.WaitOne();
lock(syncLock){
   job = newJobs.First();
    newJobs.Remove(job);
    /*do some processing*/
}
newJobEvent.Reset();

Когда выполняется строка

job=newJobs.First()

, иногда я получаю исключение, что список пуст.Я думаю, что проверка:

 if (newJobs.Count==0)
        newJobEvent.WaitOne();

также должна быть в операторе блокировки, но я боюсь тупиков в строке newJobEvent.WaitOne ();

Как я могу ее решить?

Большое спасибо и простите за длинный пост!

Ответы [ 3 ]

2 голосов
/ 20 октября 2010

Вы правы. Вызов WaitOne внутри замка может привести к тупику. И проверку, чтобы убедиться, что список пуст, нужно сделать внутри блокировки, иначе может быть гонка с другим потоком, пытающимся удалить элемент. Теперь ваш код выглядит подозрительно как шаблон «производитель-потребитель», который обычно реализуется с помощью очереди блокировки. Если вы используете .NET 4.0, вы можете воспользоваться классом BlockingCollection .

Однако позвольте мне рассказать о нескольких способах, которыми вы можете сделать это сами. Первый использует List и ManualResetEvent, чтобы продемонстрировать, как это можно сделать, используя структуры данных в вашем вопросе. Обратите внимание на использование цикла while в методе Take.

public class BlockingJobsCollection
{
  private List<Job> m_List = new List<Job>();
  private ManualResetEvent m_Signal = new ManualResetEvent(false);

  public void Add(Job item)
  {
    lock (m_List)
    {
      m_List.Add(item);
      m_Signal.Set();
    }
  }

  public Job Take()
  {
    while (true)
    {
      lock (m_List)
      {
        if (m_List.Count > 0)
        {
          Job item = m_List.First();
          m_List.Remove(item);
          if (m_List.Count == 0)
          {
            m_Signal.Reset();
          }
          return item;
        }
      }
      m_Signal.WaitOne();
    }
  }
} 

Но это не то, как я бы это сделал. Я хотел бы пойти с более простым решением ниже с использованием Monitor.Wait и Monitor.Pulse. Monitor.Wait полезен, потому что можно вызывать внутри блокировки. На самом деле, предположим, должно быть сделано таким образом.

public class BlockingJobsCollection
{
  private Queue<Job> m_Queue = new Queue<Job>();

  public void Add(Job item)
  {
    lock (m_Queue)
    {
      m_Queue.Enqueue(item);
      Monitor.Pulse(m_Queue);
    }
  }

  public Job Take()
  {
    lock (m_Queue)
    {
      while (m_Queue.Count == 0)
      {
        Monitor.Wait(m_Queue);
      }
      return m_Queue.Dequeue();
    }
  }
}
1 голос
/ 20 октября 2010

Не отвечая на ваш вопрос, но если вы используете .NET Framework 4, вы можете использовать новый ConcurrentQueue , который сделает всю блокировку за вас.

По вашему вопросу:

Один сценарий, который я могу придумать, чтобы вызвать такую ​​проблему, следующий:

  • Поток вставки входит в блокировку, вызывает newJob.Add, покидает блокировку.
  • Переключение контекста на поток удаления. Он проверяет пустоту, видит элемент, входит в заблокированную область, удаляет элемент, сбрасывает событие, которое еще даже не было установлено.
  • Переключение контекста обратно в поток вставки, событие установлено.
  • Переключение контекста обратно в поток удаления. Он проверяет пустоту, не видит элементов, ждет события - которое уже установлено, пытается получить первый элемент ... Bang!

Установите и сбросьте событие внутри замка, и все будет в порядке.

0 голосов
/ 20 октября 2010

Я не понимаю, почему удаление объекта в случае нулевого объекта должно ждать его добавления, а затем удалить его.Это выглядит против логики.

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