2011-06-05 103 views
2

我正在寫一個程序,它從文件中讀取數據包,並將每個數據包分配到指定的管道進行處理。每個管道對象都有一個阻塞隊列和一個過濾器類。可以同時運行多條這樣的管道。Thread.Sleep(Timespan.Zero)可以在這種情況下使用嗎?

阻塞隊列只是在輸入端收集數據包,直到它達到最大容量,此時它會阻塞隊列方法,直到過濾器出隊的某些數據包。阻塞隊列是一個簡單的類,它使用Monitor.Wait()Monitor.Pulse()進行同步。

每個過濾器都有一個Process()方法在其專用線程上執行。它看起來是這樣的:

public void Process() 
{ 
    while (!done) 
    { 
     var packet = sourceQueue.Dequeue(); 

     // Perform some filtering operation on packet here 

     targetQueue.Enqueue(sourceQueue); 

     // Let other threads do some work 
     Thread.Sleep(Timespan.Zero); 
    } 
} 

什麼以這種方式使用Thread.Sleep()方法的可能的陷阱?

MSDN documentation規定,使用Thread.Sleep(Timespan.Zero)「表示該線程應暫停允許其他等待線程來執行,」這是我想要的。

但是有博客條目like this one基本上說使用Thread.Sleep是邪惡的。爲什麼?這是一個很好的使用Thread.Sleep,還是有更好的方法?

+0

是不是信號燈精確設計你提到的能力的情況呢? – Mehrdad 2011-06-05 22:03:48

+0

@Mehrdad:我不知道。 MSDN文檔說Semaphore「控制對資源池的訪問」。 'ThreadPool'不會完成同樣的事情嗎? – 2011-06-05 22:11:32

+0

我的意思是信號量對有多少線程可以訪問資源有一個上限,如果我理解正確的話就是你在這裏做的,對嗎? – Mehrdad 2011-06-05 22:15:13

回答

3

真正的程序不睡覺()。

您的建議Sleep(0)還有一個問題,只允許具有相同優先級的線程運行。一般認爲Sleep(1)更安全一些。見Joe Duffy

但是在任何情況下,您的分辨率都是20ms,這可能會太長。

你所說的循環做了一點工作,然後引發一個上下文切換,非常浪費。

充分利用時間段會更好,因此請繼續工作,直到隊列阻止您。阻塞比睡眠好。

+0

'所以繼續工作,直到隊列阻塞你 - 這可能需要一段時間,只要該文件正在提供該類型的數據包,並且文件中還有其他數據包也必須被服務(通過其他管道)。我的阻擋隊列上是否需要高水位和低水位標記,以便我可以使濾波功能更加「矮胖」?或者我應該選擇任意數量的數據包來同時處理?我怎麼知道這個任意數字是什麼? – 2011-06-05 22:03:07

+1

@羅伯特:我遺漏了「或者調度員搶先了你」,這總是隱含着,並且不受你的控制。但你可以通過限制隊列來調整它,而不是通過睡眠。重新編輯:是的,隊列上的高水位標記。 – 2011-06-05 22:06:18

1

只要性能不重要,你會沒事的。

使用Thread.Sleep唯一不利的方面是,您不知道多久可以控制回來,因爲所有其他線程在重新獲取之前都會獲得處理時間。只要你不在乎在控制恢復之前20ms還是100ms,你就不會遇到任何問題。

+0

如果需要很長時間才能恢復控制,這意味着其他管道需要時鐘週期,並且我通過限制管道來控制這些管道。所以不,我真的不在乎它的20ms還是100ms。我想我應該提到這不是一個實時或接近實時的應用程序,儘管我確實需要保持足夠忙以保持應用程序I/O綁定在硬盤上。 – 2011-06-05 22:06:24

5

如果您使用.NET 4,我會親自使用Thread.Yield這樣可以很清楚你想達到什麼樣的......但除此之外,你的睡眠看起來應該是給我。 ..(取決於0或1的選擇,如其他地方所述),但你確定它確實有必要嗎?你是否執行過任何分析來檢查它是否真的有幫助?讓這個線程完成時間片的缺點是什麼?

它看起來像你不依靠睡正確性這是彼得裏奇的博客文章的主旨,據我所知......這意味着它必須是出於性能原因。與的任何爲了表現一樣,你應該真正衡量有無這個表現,看看它是否真的值得。如有疑問,請使用簡單的代碼:)

+0

這些標籤說Fx 3.5 – 2011-06-05 22:02:25

+0

@亨克:啊...我不認爲他們當我加載頁面:( – 2011-06-05 22:03:25

+0

所以你是說,這可能我不需要'睡眠()'?我可以運行我的循環,並把它留給操作系統來執行上下文切換? – 2011-06-05 22:13:53

1

如果有工作要做,那麼不要這樣做?除非你的系統有進一步的限制,否則按照John的建議,過濾所有的輸入,直到排隊是空的似乎是合理的。其他的管道線程也會做很多事情,所以他們都應該完成他們的工作。

如果您的問題是,你的「過濾操作」所做的工作是非常小的,所以你要處理他們的塊,以減輕過度的上下文切換梅比你既可以:

1)使用由較少的線程將所需的「過濾操作」傳遞給數據包,即。作爲分組的一種方法。然後較少的線程可以執行更廣泛的作業,因此可以增加每個線程的負載並減少上下文切換/數據包。如果任何線程可以執行任何過濾操作,那將是有用的,但我意識到這並非總是可行的。

2)將大量數據包對象加載到列表/隊列/堆棧上,並將此構造推送到線程隊列中。

另一個考慮幾點:

謹防「監視脈衝」和「事件信號」生產者 - 消費者隊列。我曾經看到過許多有嚴重缺陷的嘗試,特別是有多個生產者和多個CPU。消費時出現問題,因爲檢查隊列爲空以及等待監視器/事件的行爲不是一次原子操作。我還沒有相信,在一般情況下,這樣一個隊列可以做到可靠工作。如果只有一個生產者/消費者,這可能會好起來,所以你可能會好起來,但如果你的應用程序加載時出現奇怪的事情,請記住這一點。 '計算機科學117'生產者 - 消費者隊列使用信號量來生產者/消費者等待並自動計數隊列條目。您的隊列條目是需要應用「訪問控制」的「資源池」,即。正是MSDN所說的信號量所提供的。

負載管理。你正在使用有界隊列,這很好。另一種可能提供更好整體性能或者更糟糕的可能性(再次 - 根據應用程序的細節,這個建議可能有用或不可用)是通過創建系統來限制系統中可用數據包的總數它們在啓動時就是一個池(池可能是另一個PC隊列,所有的數據包都被壓入)。當池變空時,這個方案會節制所有的生產者 - 他們必須等到消費者釋放「使用過的」數據包回到池中,並且不需要有限的隊列。

RGDS, 馬丁

public void Enqueue(UInt64 key, T item) 
    { 
     while (queue.Count >= MaximumSize) 
      Thread.Sleep(TimeSpan.Zero); 

     lock (queue) 
     { 
      queue.Add(key, item); 

      if (queue.Count > PeakSize) 
       PeakSize = queue.Count; 

      Monitor.Pulse(queue); 
     } 
    } 

    public T Dequeue() 
    { 
     lock (queue) 
     { 
      while (!flushed && queue.Count < MinimumSize) 
       Monitor.Wait(queue); 

      var item = queue.First(); 
      T value = item.Value; 
      queue.Remove(item.Key); 

      return value; 
     } 
    } 
+0

'消費時出現問題,因爲檢查隊列爲空以及等待監視器/事件的行爲不是一次原子操作。 '集合對象在每個操作過程中被鎖定,使用相同的鎖定對象(實現隊列的集合)。我的理解是這使得動作成爲原子的。如果你想要的話,我可以向你展示阻塞隊列的代碼 看見了;這不是很多代碼。 – 2011-06-06 00:59:49

+0

是的請發佈。我相信,當對象被推入/彈出時,你可以安全地鎖定隊列,但是我看不到,例如,消費者如何等待可以在鎖內 - 生產者無法再推入脈衝顯示器之前的新對象。 – 2011-06-06 02:28:38

+0

我在你的答案中放入了我的阻塞隊列的'Enqueue()'和'Dequeue()'方法,並向上投票。 – 2011-06-06 02:54:23

相關問題