2010-09-26 92 views
6

我已經開發出脈衝信號,通過監視器以下列方式通用的生產者 - 消費者隊列:Monitor.Wait是否需要同步?

入隊:

public void EnqueueTask(T task) 
    { 
     _workerQueue.Enqueue(task); 
     Monitor.Pulse(_locker); 
    } 

出列:

private T Dequeue() 
    { 
     T dequeueItem; 
     if (_workerQueue.Count > 0) 
     { 
       _workerQueue.TryDequeue(out dequeueItem); 
      if(dequeueItem!=null) 
       return dequeueItem; 
     } 
     while (_workerQueue.Count == 0) 
      { 
       Monitor.Wait(_locker); 
     } 
     _workerQueue.TryDequeue(out dequeueItem); 
     return dequeueItem; 
    } 

等待部分產生以下SynchronizationLockException: 「對象同步方法是從非同步代碼塊調用的」 我需要同步嗎?爲什麼?使用ManualResetEvents還是使用.NET 4.0的Slim版本更好?

回答

6

是的,當前的線程需要「擁有」顯示器以調用WaitPulse,如記錄。 (所以你也需要鎖定Pulse。)我不知道爲什麼它是必需的細節,但它在Java中是一樣的。儘管如此,我通常會發現我想要這樣做,以使調用代碼清潔。

請注意,Wait會自行釋放顯示器,然後等待Pulse,然後在返回之前重新獲取顯示器。

至於使用ManualResetEventAutoResetEvent而是 - 你可以,但我個人更喜歡使用Monitor方法,除非我需要一些等待句柄的其他功能(如原子等待任何/所有的多手柄)。

+0

你爲什麼要這樣做?你將如何合併顯示器?只是用於顯示器的鎖定器對象的鎖定?不知道鎖是否添加了ResetEvents不需要的另一個上下文切換? – user437631 2010-09-26 13:14:00

+0

@ user437631:是的,只是一個普通的'lock'語句沒問題。這可能需要或可能不需要額外的上下文切換 - 我不認爲您有任何證據表明ResetEvents不需要它。實際上,由於它們是CLR內部對象,而不是可能交叉處理Win32對象,所以監視器比ResetEvents更輕。 – 2010-09-26 15:46:24

2

從Monitor.Wait()的描述MSDN:

釋放鎖物體上,並阻塞當前線程,直到它重新獲取鎖。

'釋放鎖'部分是問題,對象沒有鎖定。您正在將_locker對象視爲WaitHandle。做你自己的鎖定設計,證明是正確的,這是一種黑魔法,最好留給我們的醫生,傑弗裏裏克特和喬達菲。但是我給這一個鏡頭:

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

    public void Enqueue(T obj) { 
     lock (queue) { 
      queue.Enqueue(obj); 
      Monitor.Pulse(queue); 
     } 
    } 

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

在大多數你將要節流生產,因此它不能填補隊列無界任何實際生產者/消費者方案。以達菲的BoundedBuffer design爲例。如果你可以負擔得起.NET 4.0,那麼你肯定會利用它的ConcurrentQueue類,它具有更多的黑魔法,低開銷鎖定和等待旋轉。

0

,以查看Monitor.WaitMonitor.Pulse/PulseAll並不像讓系統的手段提供的等待的手段,而是(爲Wait)的正確方式知道該代碼是在等待循環,其不能退出直到感興趣的東西發生變化,並且(對於Pulse/PulseAll)作爲讓系統知道代碼剛剛改變了某些可能導致滿足退出條件的某個其他線程的等待循環的一種手段。一個應該能夠用Sleep(0)代替Wait的所有事件,並且仍然具有正確的代碼工作(即使花費CPU時間重複測試沒有改變的條件的效率要低得多)。

對於這種工作機制,就必須避免以下序列的可能性:

  • 在等待循環中的代碼測試條件時,很不滿意。

  • 另一個線程中的代碼更改條件以使其得到滿足。

  • 該另一個線程中的代碼激發鎖定(沒有人還在等待)。

  • 由於條件不滿足,等待循環中的代碼執行Wait

Wait方法需要等待的線程有鎖,因爲這是它可以確保它在等待時病情會不會它的測試時間和代碼執行的時間之間改變的唯一途徑WaitPulse方法需要一個鎖,因爲這是唯一的方法,它可以確保如果另一個線程已「提交」自己執行WaitPulse將不會發生,直到其他線程實際上這樣做。請注意,在鎖內使用Wait並不能保證它被正確使用,但在鎖外使用Wait可能是正確的。

Wait/Pulse如果雙方合作,設計實際上工作得很好。設計的最大弱點,恕我直言,(1)沒有任何機制讓線程等待,直到任何一些對象被脈衝; (2)即使有人正在「關閉」一個對象,以便所有未來的等待循環都應該立即退出(可能通過檢查退出標誌),唯一的方法是確保線程已經提交的任何Wait將得到Pulse是獲取鎖,可能無限期地等待它變得可用。