2010-05-07 379 views
3

我一直使用這個代碼作爲隊列,在Dequeue()上阻塞,直到一個元素入隊。我已經在幾個項目中使用了這些代碼幾年了,所有這些都沒有問題......直到現在。我正在看到我正在寫的一些代碼陷入僵局,並且在調查這個問題時,我的'懷疑的眼睛'已經解決了這個問題BlockingQueue<T>。我無法證明這一點,所以我想我會問一些比我更聰明的人來回顧一下潛在的問題。你們可以看到任何可能導致此代碼出現死鎖的東西嗎?這個BlockingQueue是否容易死鎖?

public class BlockingQueue<T> 
{ 
    private readonly Queue<T> _queue; 
    private readonly ManualResetEvent _event; 

    /// <summary> 
    /// Constructor 
    /// </summary> 
    public BlockingQueue() 
    { 
     _queue = new Queue<T>(); 
     _event = new ManualResetEvent(false); 
    } 

    /// <summary> 
    /// Read-only property to get the size of the queue 
    /// </summary> 
    public int Size 
    { 
     get 
     { 
      int count; 

      lock (_queue) 
      { 
       count = _queue.Count; 
      } 

      return count; 
     } 
    } 

    /// <summary> 
    /// Enqueues element on the queue 
    /// </summary> 
    /// <param name="element">Element to enqueue</param> 
    public void Enqueue(T element) 
    { 
     lock (_queue) 
     { 
      _queue.Enqueue(element); 
      _event.Set(); 
     } 
    } 

    /// <summary> 
    /// Dequeues an element from the queue 
    /// </summary> 
    /// <returns>Dequeued element</returns> 
    public T Dequeue() 
    { 
     T element; 

     while (true) 
     { 
      if (Size == 0) 
      { 
       _event.Reset(); 
       _event.WaitOne(); 
      } 

      lock (_queue) 
      { 
       if (_queue.Count == 0) continue; 

       element = _queue.Dequeue(); 
       break; 
      } 
     } 

     return element; 
    } 

    /// <summary> 
    /// Clears the queue 
    /// </summary> 
    public void Clear() 
    { 
     lock (_queue) 
     { 
      _queue.Clear(); 
     } 
    } 
} 
+2

另一方面,.NET 4現在支持內置的'System.Collections.Concurrent.ConcurrentQueue '。 – 2010-05-07 14:36:13

+1

@丹·布萊恩特:那不是阻塞隊列。 – 2010-05-07 22:42:43

+2

您可以將其封裝在新的BlockingCollection 中,該Blocking可以訪問任何潛在的「IProducerConsumerCollection 」。見http://msdn.microsoft.com/en-us/library/dd997371%28v=VS.100%29.aspx – 2010-05-07 23:50:09

回答

7

我認爲這可能是你的問題:

Thread 1     Thread 2 
Dequeue 
          Enqueue  
if (Size == 0)        // Thread 1 gets the lock 
          lock (_queue) // Thread 2 has to wait 
return _queue.Count       // Thread 1 sees: Size == 0 
          _queue.Enqueue // Thread 2 gets the lock 
          _event.Set  
_event.Reset        // uh oh 
_event.WaitOne        // now Dequeue's going to block 
              // until Enqueue gets called again 
              // (even though queue isn't empty) 
+0

這是一個可視化的好方法。謝謝,我實際上認爲這是我遇到的確切情況。這真是太神奇了,我從未見過它(我已經使用了幾年)。 – 2010-05-07 13:45:32

+0

@ unforgiven3:呃,當你考慮到這種情況發生時,你的競爭條件是如何發生的,或許不是那麼令人驚訝。這就是爲什麼編寫多線程代碼會給開發人員造成噩夢的原因。 (無論如何,我知道它確實對*我*) – 2010-05-07 13:56:54

+0

是的,我最糟糕的代碼噩夢是關於多線程的代碼,哈哈。 – 2010-05-07 14:00:48

1

此代碼在幾個方面被打破。這裏有一個場景。 if (Size == 0)_event.Reset()之間有競爭條件。 Enqueue可能會在兩者之間觸發,其信號將會丟失。

無限長的BlockingQueue更容易用信號量實現。

+2

我很欣賞這種迴應,我會研究這些領域,但拜託,態度是不需要的。我在尋求幫助。你的答案幫助我重新評估了這一點,並且我發現了另一個BlockingQueue,我將使用這個BlockingQueue(http://stackoverflow.com/questions/530211/creating-a-blocking-queuet-in-net/530228#530228) – 2010-05-07 13:44:16

+1

我不認爲你的第一段是真的。因爲如果size是0,他只調用'_event.WaitOne',所以當「不能再清空隊列」時,將不會有狀態。相反,「出隊」會阻止比預期更長的時間。但是重新調用'Enqueue'將一直保持隊列形狀(直到下一次比賽狀態)。或者我錯過了什麼? – 2010-05-07 13:53:17

+1

我對任何冒犯我的人表示歉意;特別是OP。我的印象是,OP正在使用別人的圖書館,並想知道是否保留它。我有一個非常棒的記錄,你可以自己觀察,以防你認爲這是我的典型反應。 FWIW,我修改了答案。 – 2010-05-07 22:34:29

0

我不知道您的要求,還有什麼類的做法,但如果你可以使用.NET 4,你可能要考慮使用ConcurrentQueue<T>BlockingCollection<T>一起使用,應該給你一個阻塞隊列。