2011-06-13 66 views
10

給定下面的代碼片斷(在學習線程時在某處找到)。Monitor.Wait,條件變量

public class BlockingQueue<T> 
    { 
     private readonly object sync = new object(); 
     private readonly Queue<T> queue; 
     public BlockingQueue() 
     { 
      queue = new Queue<T>(); 
     } 

     public void Enqueue(T item) 
     { 
      lock (sync) 
      { 
       queue.Enqueue(item); 
       Monitor.PulseAll(sync); 
      } 

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

       return queue.Dequeue(); 
      } 

     } 
    } 

我想了解的是,

爲什麼會有一個while循環?

while (queue.Count == 0) 
      Monitor.Wait(sync); 

,什麼是錯的,

if(queue.Count == 0) 
     Monitor.Wait(sync); 

其實,一切的時候,我看到了類似的代碼,我發現使用while循環,任何人都可以請幫我瞭解使用一個在另一個之上。 謝謝。

+0

您可能需要指定語言並添加適當的標籤。 – 2011-06-13 06:36:11

+0

忘記了,有人已經添加了C#標籤。 – 41K 2011-06-13 06:41:32

+1

我來幫忙! – Default 2011-06-13 06:45:50

回答

18

您需要了解Pulse,PulseAllWait正在做什麼。 Monitor維護兩個隊列:等待隊列和就緒隊列。當線程調用Wait時,它被移入等待隊列。當一個線程調用Pulse時,它將中的一個且只有一個線程從等待隊列移動到就緒隊列。當線程調用PulseAll時,它將全部線程從等待隊列移動到就緒隊列。就緒隊列中的線程可隨時重新獲取鎖定,但只能在當前持有者發佈鎖定之後才能重新獲取鎖定。

基於這些知識,很容易理解爲什麼在使用PulseAll時必須重新檢查隊列計數。這是因爲所有出隊線程最終都會喚醒,並且會嘗試從隊列中提取項目。但是,如果隊列中只有一個項目開頭,會怎麼樣?顯然,我們必須重新檢查隊列計數以避免空隊列的出隊。

那麼如果您使用Pulse而不是PulseAll那麼結論如何?簡單的if檢查仍然存在問題。原因是因爲來自就緒隊列的線程不一定會成爲獲取鎖的下一個線程。這是因爲Monitor並不優先於Enter呼叫上方的Wait呼叫。

當使用Monitor.Wait時,while循環是相當標準的模式。這是因爲脈衝線程本身並不具有語義含義。這只是一個信號,鎖定狀態已經改變。當線程在Wait上被阻塞後喚醒時,它們應該重新檢查原來用於阻塞線程的相同條件,以查看線程現在是否可以繼續。有時它不能,所以它應該阻止更多。

拇指這裏最好的規則是,如果有關於是否使用if支票或while檢查,然後總是選擇一個while循環,因爲它是更安全的疑問。事實上,我會將此置於極端,並建議始終使用使用while循環,因爲使用更簡單的if檢查沒有固有優勢,因爲無論如何if檢查幾乎總是錯誤的選擇。類似的規則適用於選擇是使用Pulse還是PulseAll。如果對使用哪一個有疑問,則始終選擇PulseAll

+0

+1非常有用的監視器使用指南。事實上,沒有雙重鎖定的'如果'不過是一種調試災難。 – 9dan 2011-06-14 06:13:11

+0

@Brain Gideon。真棒解釋! – 41K 2012-07-18 06:30:03

+0

你不覺得.NET Monitor在設計上有缺陷嗎?不應該只有兩個隊列;應該有多於一個等待隊列,每個等待隊列用於由<鎖定,條件變量>對指定的特定等待條件,如pthread所做的那樣。當一個線程只喚醒查找等待條件根本沒有改變時,有一個大的等待隊列會導致過度的上下文切換,然後立即返回到等待隊列。 – h9uest 2015-01-26 17:06:26

3

您必須繼續檢查隊列是否仍爲空。只使用if只會檢查一次,稍等片刻,然後出隊。如果那個時候隊列還是空的呢?砰!排隊溢錯誤......

+0

但是一個等待只有在脈衝之後纔會醒來,而當某些入隊時會發生這種情況。 – 2011-06-13 09:50:39

1

如果條件時發佈的東西鎖的queue.Count == 0不再進行檢查,也許隊列溢錯誤,所以我們有充分的,因爲時間來檢查條件併發,這就是所謂的紡紗

-1
if (queue.Count == 0) 

會做。

使用While循環模式「等待和檢查條件」的上下文是遺留的剩餘,我想。由於非Windows,非.NET監視器變量可以在沒有實際的情況下觸發Pulse

在.NET中,如果沒有Queue填充,則無法觸發您的私有監視變量,因此在監視器等待後您不必擔心隊列下溢。但是,使用while循環爲「等待和檢查條件」確實是不好的習慣。

+0

對我來說聽起來很複雜,請您指出一些資源,我可以理解您的意思。 – 41K 2011-06-13 07:10:13

+0

嗯..恐怕我無法提供更詳細的資料。這是來自我自己的經驗。我在Windows中使用了'if ...'多年,並且沒有問題。當我在Linux中使用'if ...'編碼?都搞砸了。您需要在* Nix平臺中使用while循環來監視(條件)變量。之後,我總是使用while循環,而不管語言/平臺。 – 9dan 2011-06-13 07:26:27

+2

恕我直言,一個簡單的'如果'不會這樣做。如果在此期間另一個線程「出隊」,則可能會遇到競爭條件。我懷疑這取決於PulseAll或Pulse是否被調用...... – 2011-06-13 10:31:17

0

爲什麼在Unix上它可能出錯是因爲虛假的喚醒,OS信號造成的可能性。這是一個副作用,不保證在Windows上不會發生。這不是遺留問題,它是操作系統的工作原理。如果監視器是以條件變量的形式實現的,那就是。

DEF:一個虛假喚醒是在一個條件變量等待網站休眠線程的重新調度,這是不是從當前的程序線程(如Pulse())未來的行動觸發。

這種不便可能在託管語言中被掩蓋,例如,隊列。因此,在退出Wait()函數之前,框架可以檢查這個正在運行的線程實際上是否被請求進行調度,如果它沒有在運行隊列中找到它,它可以重新進入睡眠狀態。隱藏問題。