我在ASP.NET中有一个生产者 - 消费者场景。我设计了一个Producer
类,一个Consumer
类和一个用于保存共享对象并负责生产者和消费者之间通信的类,我们称之为Mediator
。因为我在启动时(在父对象中)分叉执行路径,一个线程将调用Producer.Start()
而另一个线程调用Consumer.Start()
,我需要将Mediator
的引用传递给两个{ {1}}和Producer
(通过Consumer
)。 Constructor
是一个智能类,它将优化许多内容,例如内部队列的长度,但现在将其视为循环阻塞队列。 Mediator
将将新对象排入Producer
,直到队列变满,然后Mediator
才会阻止。 Producer
将Consumer
中的对象从<{1}}中取消,直到队列中没有任何内容为止。对于线程之间的信令,我在Mediator
类中实现了两个方法:Mediator
和Wait()
。代码是这样的:
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>
由于
答案 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不存储任何状态?这意味着如果Producer
在Pulse
之前/之后调用Consumer
来调用Wait
,则Wait
将会阻止。这是Monitor.Pulse
另外,您应该知道object x = new object();
是无关紧要的 - 外拨会初始化x
,因此创建的对象将在TryDequeue
调用后超出范围。
答案 4 :(得分:1)
很难说明提供的代码示例。
直到Mediator类和你的制作者类中有更多细节可用,这是一些疯狂的猜测。看起来有些线程可能会在您不期望它时持有锁。一旦你发出脉冲,你需要通过退出“锁定”范围释放任何线程中的锁。因此,如果Mediator中的某个位置有锁定然后调用Pulse,则需要退出锁定所在的最外层范围,而不仅仅是Pulse中的范围。
答案 5 :(得分:1)
你能重构一个普通的消费者/生产者队列吗?然后,它可以处理单个类中的enqueing和dequing以及线程信号,因此不需要传递公共锁。然后可以通过代表处理Dequeing过程。如果你愿意,我可以发一个例子。