Можете ли вы сигнализировать и ждать атомно с синхронизацией потока C #? - PullRequest
4 голосов
/ 24 мая 2011

У меня проблемы с синхронизацией потоков в C #. У меня есть общий объект, которым манипулируют два потока, я сделал доступ к объекту взаимоисключающим, используя lock (), но я также хочу заблокировать каждый поток в зависимости от состояния общего объекта. Специально блокируйте поток A, когда объект пуст, блокируйте поток B, когда объект заполнен, и пусть другой поток сигнализирует заблокированный поток, когда состояние объекта изменяется.

Я попытался сделать это с ManualResetEvent, но столкнулся с состоянием гонки, когда поток B обнаружит, что объект заполнен, переместится в WaitOne, и поток A войдет и опустошит объект (сигнализируя MRE о каждом доступе и заблокировать себя, как только объект пуст), прежде чем поток A достигнет своего WaitOne, что означает, что поток A ожидает, пока поток не будет заполнен, даже если это не так.

Я полагаю, что если бы я мог вызвать такую ​​функцию, как 'SignalAndWaitOne', которая бы атомарно сигнализировала перед ожиданием, это предотвратило бы это состояние гонки?

Спасибо!

Ответы [ 2 ]

7 голосов
/ 24 мая 2011

Типичный способ сделать это - использовать Monitor.Enter, Monitor.Wait и Monitor.Pulse для управления доступом к общей очереди.Эскиз:

shared object sync = new object()
shared Queue q = new Queue()

Producer()
    Enter(sync)
    // This blocks until the lock is acquired
    while(true)
        while(q.IsFull)
            Wait(sync)     
            // this releases the lock and blocks the thread 
            // until the lock is acquired again

        // We have the lock and the queue is not full.
        q.Enqueue(something)
        Pulse(sync)
        // This puts the waiting consumer thread to the head of the list of 
        // threads to be woken up when this thread releases the lock

Consumer()
    Enter(sync)
    // This blocks until the lock is acquired
    while(true)
        while(q.IsEmpty)
            Wait(sync)
            // this releases the lock and blocks the thread 
            // until the lock is acquired again

        // We have the lock and the queue is not empty.
        q.Dequeue()
        Pulse(sync)
        // This puts the waiting producer thread to the head of the list of 
        // threads to be woken up when this thread releases the lock
5 голосов
/ 24 мая 2011

A BlockingCollection уже предоставлено .NET 4.0.

Если у вас более ранняя версия, вы можете напрямую использовать класс Monitor.

РЕДАКТИРОВАТЬ: Следующий код полностью не протестирован и не обрабатывает maxCount значения, которые являются маленькими (<= 2). В нем также нет положений о тайм-аутах или отмене: </p>

public sealed class BlockingList<T>
{
  private readonly List<T> data;
  private readonly int maxCount;

  public BlockingList(int maxCount)
  {
    this.data = new List<T>();
    this.maxCount = maxCount;
  }

  public void Add(T item)
  {
    lock (data)
    {
      // Wait until the collection is not full.
      while (data.Count == maxCount)
        Monitor.Wait(data);

      // Add our item.
      data.Add(item);

      // If the collection is no longer empty, signal waiting threads.
      if (data.Count == 1)
        Monitor.PulseAll(data);
    }
  }

  public T Remove()
  {
    lock (data)
    {
      // Wait until the collection is not empty.
      while (data.Count == 0)
        Monitor.Wait(data);

      // Remove our item.
      T ret = data.RemoveAt(data.Count - 1);

      // If the collection is no longer full, signal waiting threads.
      if (data.Count == maxCount - 1)
        Monitor.PulseAll(data);
    }
  }
}
...