Monitor.Wait()和Monitor.Pulse()的线程问题

时间:2010-10-18 01:43:38

标签: c# asp.net multithreading

我在ASP.NET中有一个生产者 - 消费者场景。我设计了一个Producer类,一个Consumer类和一个用于保存共享对象并负责生产者和消费者之间通信的类,我们称之为Mediator。因为我在启动时(在父对象中)分叉执行路径,一个线程将调用Producer.Start()而另一个线程调用Consumer.Start(),我需要将Mediator的引用传递给两个{ {1}}和Producer(通过Consumer)。 Constructor是一个智能类,它将优化许多内容,例如内部队列的长度,但现在将其视为循环阻塞队列。 Mediator新对象排入Producer,直到队列变满,然后Mediator才会阻止。 Producer Consumer中的对象从<{1}}中取消,直到队列中没有任何内容为止。对于线程之间的信令,我在Mediator类中实现了两个方法:MediatorWait()。代码是这样的:

Pulse()

Inside Mediator我每次排队 Dequeued 时都会使用Class Mediator { private object _locker = new object(); public void Wait() { lock(_locker) Monitor.Wait(_locker); } public void Pulse() { lock(_locker) Monitor.Pulse(_locker); } } // This way threads are signaling: Class Consumer { object x; if (Mediator.TryDequeue(out x)) // Do something else Mediator.Wait(); } 所以等待线程会发出信号并继续工作。

但是我遇到了死锁,因为我从来没有将这种设计用于信令线程,我不确定设计是否有问题或者我在其他地方做错了什么? / p>

由于

6 个答案:

答案 0 :(得分:8)

这里没有太多代码可以继续,但我最好的猜测是你有一个live-lock问题。如果在Mediator.Pulse之前调用Mediator.Wait,则即使队列中存在某些内容,信号也会丢失。以下是实现阻塞队列的标准模式。

public class BlockingQueue<T>
{
  private Queue<T> m_Queue = new Queue<T>();

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

  public T Dequeue()
  {
    lock (m_Queue)
    {
      while (m_Queue.Count == 0) 
      {
        Monitor.Wait(m_Queue);
      }
      return m_Queue.Dequeue();
    }
  }
}

注意如何在队列为空时调用Monitor.Wait。还要注意在while循环中如何调用它。这是因为Wait优先于Enter,因此即使对Dequeue的调用已准备好返回,进入Wait的新主题也可能占用最后一项。如果没有循环,线程可能会尝试从空队列中删除项目。

答案 1 :(得分:4)

如果您可以使用.NET 4,最好的办法是使用BlockingCollection<T>(http://msdn.microsoft.com/en-us/library/dd267312.aspx)来处理排队,出列和队列长度限制。

答案 2 :(得分:2)

设计没有错。

当您使用Monitor.Wait()Monitor.Pulse()时,如果您不知道哪个线程将首先执行它(生产者或消费者),则会出现问题。在这种情况下,使用AutoResetEvent可以解决问题。当消费者到达应该消费生产者产生的数据的部分时,可以考虑消费者。也许它在生产者脉冲之前到达那里,然后一切都好但是如果消费者在生产者发出信号后到达那里。是的,然后你遇到了死锁,因为生产者已经为该部分调用了Monitor.Pulse()而不会重复它。 使用AutoResetEvent你确定消费者在那里等待来自生产者的信号,如果生产者已经在消费者甚至到达该部分之前已经发出信号,则门打开并且消费者将继续。

可以在Mediator中使用Monitor.Wait()Monitor.Pulse()来表示等待线程的信号。

答案 3 :(得分:1)

是否可能发生死锁,因为Pulse不存储任何状态?这意味着如果ProducerPulse之前/之后调用Consumer来调用Wait,则Wait将会阻止。这是Monitor.Pulse

documentation中的注释

另外,您应该知道object x = new object();是无关紧要的 - 外拨会初始化x,因此创建的对象将在TryDequeue调用后超出范围。

答案 4 :(得分:1)

很难说明提供的代码示例。

  • 锁在其他地方吗?在Mediator?
  • 线程是否只是在获取锁定而不是实际的等待呼叫时停放?
  • 您是否暂停了调试器中的线程以查看当前状态是什么?
  • 你是否尝试过一个简单的测试,只需在队列中放一个简单的单值并使其工作?或者Mediator在这一点上相当复杂?

直到Mediator类和你的制作者类中有更多细节可用,这是一些疯狂的猜测。看起来有些线程可能会在您不期望它时持有锁。一旦你发出脉冲,你需要通过退出“锁定”范围释放任何线程中的锁。因此,如果Mediator中的某个位置有锁定然后调用Pulse,则需要退出锁定所在的最外层范围,而不仅仅是Pulse中的范围。

答案 5 :(得分:1)

你能重构一个普通的消费者/生产者队列吗?然后,它可以处理单个类中的enqueing和dequing以及线程信号,因此不需要传递公共锁。然后可以通过代表处理Dequeing过程。如果你愿意,我可以发一个例子。