2011-06-17 71 views
2

我有一个多线程应用程序。.NET线程 - 同步对象

一个线程插入到一个队列中,许多线程读取形成此队列。为了正确读取,阅读器线程像下面的代码一样锁定队列。

我的问题是:插入程序线程在读取线程调用以下代码时是否被阻塞,因为它使用相同的队列?还是继续插入而不中断?

lock (MsgQueue) { 
    if (MsgQueue.Count == 0) { 
     Monitor.Wait(MsgQueue); 
     continue; 
    } 
    msg = MsgQueue.Dequeue(); 
} 
+0

要验证您在评论中所说的内容,可以发表一篇关于Enqueue如何的短文吗? – 2011-06-17 12:49:23

+0

如果你使用的是.Net 4.0,你可以在这里使用`ConcurrentQueue`,参见http://msdn.microsoft.com/en-us/library/dd267265.aspx – 2011-06-17 15:20:19

+0

我建议你使用[BlockingCollection](http:如果C#4.0或队列与[AutoRestEvent](http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent。 aspx)检查[这在stacoverflow](http://stackoverflow.com/questions/6397566/multithreaded-net-queue-problems-c/6404581#6404581) – 2011-06-20 04:07:24

回答

1

插入程序线程正在阻止点,是的。

lock (MsgQueue) { 
     if (MsgQueue.Count == 0) { // LINE 1 
      Monitor.Wait(MsgQueue); // LINE 2 
      continue; 
     } 
     msg = MsgQueue.Dequeue(); // LINE 3 
    } 

在第1行锁由读卡器保持,所以插入器被锁住。

在第2行锁定被释放,直到插入器大概在MsgQueue上调用Monintor.Pulse才被重新获取。

在第3行,锁仍在被保持(从第1行开始),之后由于退出lock范围而被再次释放。

1

如果插入器线程调用lock (MsgQueue)那么显然它会阻止每当阅读器之一已锁定该队列

2

另一个线程将由锁(MsgQueue)被阻止,而该线程处于lock而不是当在Monitor.Wait(它释放锁,所以其他线程可以Pulse)。

这是条件变量模式:在处理共享状态(队列实例)时保持锁定,但在等待条件更改(Monitor.Wait)时释放它。

更新:基于评论:

没有它插入简单。插入器没有锁定

然后队列对象很可能被损坏。除非您使用的队列类型本质上是线程安全的,否则您必须使用相同的锁进行所有操作。如果此队列主要用于将对象从一组(源)线程传输到另一组((工作)线程(其中每组可能只是一个)),那么您应该考虑ConcurrentQueue这是线程安全的(尽管您需要类似event这样的信号来指示队列上有某些东西来避免员工轮询)。

1

不,我认为你的问题是关于lock (MsgQueue)的含义,隐喻可能有点误导。锁定对象不会以任何方式更改该对象的状态,也不会阻止其他线程,除非这些线程也在同一对象上使用lock

这就是为什么你经常会看到这样(更好)模式:

private Queue<MyClass> _queue = ...; 
private object _queueLock = new object(); 
... 
lock(_queueLock) 
{ 
    _queue.Enqueue(item); 
} 

在锁使用的参考仅作为一个“票”。

2

是的,生产者(或插入者)将在消费者持有锁的同时被阻止。请注意,通过调用Monitor.Wait释放该锁,然后在控制流返回给调用者时重新获取该锁。所有这些都假设你的制作者试图获得相同的锁定。

作为一个方面说明,你有消费者编码的方式效率稍低一些。因为你有一个continue陈述,我不得不假设while循环包装了lock,这可能使你的代码看起来更像以下内容。

object msg = null; 
while (msg == null) 
{ 
    lock (MsgQueue) 
    { 
    if (MsgQueue.Count == 0) 
    { 
     Monitor.Wait(MsgQueue); 
     continue; 
    } 
    msg = MsgQueue.Dequeue(); 
    } 
} 

这可能进行重构,以便等待条件的lock块内复查。这样您就不必释放并重新获取锁来执行检查。

object msg = null; 
lock (MsgQueue) 
{ 
    while (MsgQueue.Count == 0) 
    { 
    Monitor.Wait(MsgQueue); 
    } 
    msg = MsgQueue.Dequeue(); 
} 

再次,因为我看到了continue声明我假设大家都知道,等待条件必须总是Wait后复查。但是,如果你不知道这个要求,我会在这里说明它,因为它很重要。

如果等待条件没有被重新检查,并且有2个或更多的消费者,那么他们中的一个可以进入锁内,并将最后一个项目出列。即使其他消费者通过致电PulsePulseAll从等候队列移动到就绪队列,但这仍然可能发生,但它没有机会在第一个消费者面前重新获取锁定。显然,如果没有重新检查,消费者可能会尝试在空队列上运行。在生产端是否使用PulsePulseAll并不重要。仍然存在问题,因为Monitor不优先于Enter之上的Wait

更新:

我忘了指出,如果你使用的是.NET 4.0,那么你可以采取的BlockingCollection这是一个阻塞队列的实现优势。对于多个生产者和消费者来说这是安全的,并且如果队列为空,则为你完成所有阻塞。