2009-04-27 72 views
11

這不是關於我可以或應該以最佳方式利用隊列的不同方法,而是我看到的發生的事情對我來說毫無意義。C#線程和隊列

void Runner() { 
    // member variable 
    queue = Queue.Synchronized(new Queue()); 
    while (true) { 
     if (0 < queue.Count) { 
      queue.Dequeue(); 
     } 
    } 
} 

這是運行在一個單獨的線程:

var t = new Thread(Runner); 
t.IsBackground = true; 
t.Start(); 

其他事件是 「排隊」 荷蘭國際集團其他地方。我看到發生的事情經過一段時間後,Dequeue實際上會拋出InvalidOperationException,隊列爲空。這應該是不可能的,因爲計數如何保證那裏存在某些東西,而且我確信沒有其他東西是「出列」的。

的問題(S):

  1. 是否有可能入隊實際上增加了計數之前的產品完全隊列(這意味着什麼)?
  2. 是否有可能線程以某種方式在Dequeue語句處重新啓動(過期,重置......),但是在它已經移除了一個項目後立即重啓?

編輯(澄清):

這些碼片是一個實現背景輔助線程的包裝類的一部分。這裏的出隊隊列是唯一的出隊隊列,並且所有入隊/出隊隊列都在同步成員變量(隊列)上。

+0

由於Ryan的回答......這是真正的代碼還是隻是一個簡單的例子?如果它是真正的代碼,你應該考慮改變循環 - 輪詢隊列而不是讓讀者與作者同步是一個糟糕的設計。您正在浪費數百萬個處理器週期來加熱房間。 – 2009-04-27 16:59:00

+0

這是一個能夠解決問題的例子。那裏有一個Thread.Sleep,處理器沒有被敲打。我之所以選擇輪詢過程而不是同步讀/寫器,是因爲隊列幾乎總是有一些東西。在我們的主幹中,儘管我添加了一個AutoResetEvent來玩弄。 就像我在頂部所說的那樣,我並不擔心這裏的實現。這個線程模型似乎存在一個真正的問題,無論是對還是錯。 – neouser99 2009-04-27 17:23:49

+0

從看你的代碼,你至少有主線程和你認爲Dequeue被調用的線程。爲什麼不給線程命名,並且每次調用Dequeue時,都使用堆棧跟蹤記錄線程的名稱。您可能會發現主線程中的某些內容會以您不期待的方式運行。 – 2009-04-27 19:11:38

回答

11

使用反射器,您可以看到不,計數直到添加項目後纔會增加。

正如Ben指出的那樣,看起來確實如此,因爲您確實有多人致電出列。

你說你沒有別的要求出列。那是因爲你只有一個線程叫出隊?是否在其他地方叫出隊?

編輯:

我寫了一個小樣本代碼,但不能讓問題重現。它只是保持運行和運行,沒有任何例外。

在得到錯誤之前它運行了多長時間?也許你可以分享更多的代碼。

class Program 
{ 
    static Queue q = Queue.Synchronized(new Queue()); 
    static bool running = true; 

    static void Main() 
    { 
     Thread producer1 = new Thread(() => 
      { 
       while (running) 
       { 
        q.Enqueue(Guid.NewGuid()); 
        Thread.Sleep(100); 
       } 
      }); 

     Thread producer2 = new Thread(() => 
     { 
      while (running) 
      { 
       q.Enqueue(Guid.NewGuid()); 
       Thread.Sleep(25); 
      } 
     }); 

     Thread consumer = new Thread(() => 
      { 
       while (running) 
       { 
        if (q.Count > 0) 
        { 
         Guid g = (Guid)q.Dequeue(); 
         Console.Write(g.ToString() + " "); 
        } 
        else 
        { 
         Console.Write(" . "); 
        } 
        Thread.Sleep(1); 
       } 
      }); 
     consumer.IsBackground = true; 

     consumer.Start(); 
     producer1.Start(); 
     producer2.Start(); 

     Console.ReadLine(); 

     running = false; 
    } 
} 
3

以下是我認爲有問題的順序是:

  1. (0 < queue.Count)計算結果爲真,隊列不爲空。
  2. 此線程獲取preempted並運行另一個線程。
  3. 另一個線程從隊列中刪除一個項目,清空它。
  4. 此線程恢復執行,但現在位於if塊內,並嘗試將空列表出列。

但是,你說沒有別的離隊......

嘗試輸出,如果塊內的計數。如果您看到計數跳躍數字向下,則其他人正在離隊。

3

下面是從the MSDN page一個可能的答案對這個話題:

通過集合進行枚舉 本質上不是一個線程安全的 程序。即使集合是 同步,其他線程仍然可以 修改集合,這將導致枚舉器引發 引發異常。 要在枚舉中保證線程安全,您可以在整個 枚舉期間鎖定 集合,或者捕獲由其他 線程所做更改產生的異常 。

我的猜測是,你是正確的 - 在某些時候,有一個競爭條件發生的事情,以及你最終離隊的東西是不存在的。

互斥或Monitor.Lock在這裏可能是適當的。

祝你好運!

2

「排隊」數據的其他區域是否也使用相同的同步隊列對象?爲了使Queue.Synchronized成爲線程安全的,所有Enqueue和Dequeue操作必須使用同一個同步隊列對象。

MSDN

爲了保證 隊列的線程安全的,所有的操作都必須通過僅此包裝進行 。

編輯: 如果您遍歷涉及大量的計算,或者如果您使用的是長期線程循環(通信等),你應該考慮有一個等待功能,如System.Threading.Thread.Sleep許多項目,System.Threading.WaitHandle.WaitOneSystem.Threading.WaitHandle.WaitAllSystem.Threading.WaitHandle.WaitAny,否則可能會導致系統性能下降。

1

問題1:如果你使用的是同步隊列,那麼:不,你很安全!但是您需要使用雙方的同步實例,供應商和饋線。

問題2:當沒有工作要做時,終止你的工作線程是一件簡單的工作。但是,無論哪種方式都需要一個監視線程,或者只要隊列有事要做,隊列就會啓動一個後臺工作線程。最後一個聽起來更像是ActiveObject模式,而不是一個簡單的隊列(這是Single-Responsibily-Pattern說它只應該排隊)。

此外,我會去一個阻塞隊列,而不是上面的代碼。即使沒有工作要做,你的代碼工作方式也需要CPU的處理能力。阻塞隊列讓您的工作線程在任何時候都無法進入睡眠狀態。您可以在不使用CPU處理能力的情況下運行多個睡眠線程。

C#沒有帶有阻塞隊列實現,但有很多。看到這個example和這個one