2011-06-17 70 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這是一個阻塞隊列的實現優勢。對於多個生產者和消費者來說這是安全的,並且如果隊列爲空,則爲你完成所有阻塞。