2010-08-15 38 views
14

特別是,我正在考慮使用TPL來啓動(並等待)外部進程。在決定開始另一項任務之前,TPL是否會考慮總的機器負載(包括CPU和I/O)(因此 - 在我的情況下 - 是另一個外部過程)?任務並行庫(或PLINQ)是否考慮其他進程?

例如:

我已經得到了需要進行編碼或轉碼(例如,從WAV後手或FLAC到MP3)約100媒體文件。編碼是通過啓動外部進程(例如FLAC.EXE或LAME.EXE)完成的。每個文件大約需要30秒。每個進程大都是CPU限制的,但是其中有一些I/O。我有4個內核,所以最壞的情況(通過將解碼器編碼到編碼器中進行代碼轉換)仍然只使用2個內核。我想這樣做:

Parallel.ForEach(sourceFiles, 
    sourceFile => 
     TranscodeUsingPipedExternalProcesses(sourceFile)); 

這是否會揭開序幕100個任務(因此200的外部進程的CPU競爭)?還是會看到CPU很忙,一次只能做2-3個?

+0

當然,我應該做的是將一個TaskCompletionSource連接到Process.Exited事件,然後有一個返回Task的TranscodeAsync方法。它會是非阻塞的。然後,我可以對任務進行更細緻的控制,同時仍然呆在第三方物流的糧食中。 – 2012-02-03 14:19:17

回答

21

你會在這裏遇到幾個問題。調度程序的飢餓迴避機制會將您的任務視爲等待進程時被阻止。它會發現很難區分死鎖線程和簡單等待進程完成的線程。因此,如果您的任務運行或很長時間(見下文),它可能會安排新的任務。登山啓發式應考慮系統的整體負載,無論是從應用程序還是其他應用程序。它只是試圖最大限度地完成工作,所以它會增加更多的工作,直到系統的整體吞吐量停止增加,然後退出。我不認爲這將影響您的應用程序,但避免問題的可能會。

你可以找到更多的細節,如何這在Parallel Programming with Microsoft®.NET,科林坎貝爾,拉爾夫·約翰遜,米勒阿德,斯蒂芬Toub(是online早期草案)所有的作品。

「的.NET線程池自動根據管理工作者在游泳池 線程數它添加和刪除線程內置 啓發式的.NET線程池具有注入 線程兩種主要機制:A飢餓避免機制,增加了工人 線程,如果它看到在排隊的物品,並試圖在使用作爲 幾個線程儘可能地最大化吞吐量爬山 啓發而毫無進展。

飢餓避稅的目的是爲了防止死鎖這種類型的死鎖可能發生在工作人員th讀取等待同步 事件,該事件只能由線程池的全局或本地隊列中仍處於待執行 的工作項目滿足。如果有一個固定的 工作者線程數,並且所有這些線程同樣被阻止,則系統將無法取得進一步的進展。 添加新的工作線程可解決問題。

爬山啓發式算法的一個目標是在線程被I/O或其他等待條件阻塞的等待條件 阻止處理器時提高內核的利用率 。默認情況下,託管線程池每個核心有一個 工作線程。如果其中一個工作線程變爲 被阻止,則核心可能未充分利用,這取決於計算機整體工作負載上的 。線程注入邏輯 不區分被阻塞的線程和正在執行冗長的處理器密集型操作的線程 。因此,每當線程池的全局或本地隊列包含待定的 工作項時,需要花費很長時間運行的活動工作項(大於 半秒)纔會觸發新線程池工作線程的創建 線程。

.NET線程池有機會每工作項完成 時間或以500毫秒間隔注入線程,無論哪個 更短。線程池使用此機會嘗試添加線程 (或將它們帶走),並根據線程數的以前更改的反饋進行指導。如果添加線程似乎有助於吞吐量,則線程池會增加更多;否則,它會減少工作線程的數量。這種技術被稱爲爬山啓發式。 因此,保持單個任務簡短的一個原因是爲了避免 「飢餓檢測」,但是另一個保持簡短的原因是 給線程池更多的機會來提高吞吐量,調整線程數量爲 。單個任務的持續時間越短,線程池可以測量吞吐量的頻率越高,並且相應地調整線程計數。

爲了使這個具體,請考慮一個極端的例子。假設你有一個複雜的財務模擬和 操作,其中每個操作需要花費10分鐘的時間才能完成,需要500個處理器密集的 操作。如果您在全局隊列中爲這些操作的每個 創建頂級任務,則會發現大約五分鐘後,線程池將增長到500個工作線程。原因在於 線程池將所有任務視爲被阻止,並開始以每秒大約兩個線程的速率添加新的線程。

500工作線程有什麼問題?原則上,沒有任何內容,如果你有500個內核供他們使用,並且海量內存系統爲 。事實上,這是並行計算的長期願景。 但是,如果您的計算機上沒有多個內核,則在多個線程正在競爭時間片的情況下,您的系統爲 。這種情況被稱爲處理器超額訂購。允許許多處理器密集型線程在單個內核上競爭時間會增加上下文切換開銷,這會嚴重降低整個系統的吞吐量。即使你沒有耗盡內存,在這個 的情況下的性能可能會比連續計算更糟糕得多。 (每個上下文切換需要6,000到8,000個處理器週期。) 上下文切換的開銷不是唯一的開銷來源。 .NET中的託管線程佔用大約1兆字節的堆棧空間,無論該空間是否用於當前正在執行的功能。 需要大約200,000個CPU週期來創建一個新線程,並且大約需要100,000個週期來退出一個線程。這些是昂貴的操作。

只要你的任務不需要每個分鐘,線程池的登山算法就會最終意識到它有太多線程 並自行減少。但是,如果你的任務 佔用一個工作線程幾秒或幾分鐘或幾小時,那麼 將拋出線程池的啓發式,此時 應考慮替代方案。

第一個選項是將您的應用程序分解爲更短的 任務,這些任務的完成速度足以使線程池成功執行 控制線程數以獲得最佳吞吐量。 第二種可能性是實現您自己的任務調度程序 不執行線程注入的對象。如果您的任務持續時間很長,則不需要高度優化的任務調度程序,因爲與任務的執行時間 相比,調度的成本可以忽略不計。 MSDN®開發人員計劃有一個 簡單任務調度程序實現的示例,該實現限制併發的最大度數 。有關更多信息,請參閱本章末尾的「進一步閱讀」部分, 。

作爲最後的手段,您可以使用SetMaxThreads方法 配置ThreadPool類有上限的工作線程的數量 ,通常等於(這是 Environment.ProcessorCount屬性)核心數量。此上限適用於 的整個過程,包括所有的應用程序域「

+0

+1'Parallel.ForEach'的重載也可以幫助將'ParallelOptions'對象與'MaxDegreeOfParallelism'一起使用。 – 2010-09-02 02:14:02

+0

+1我找不到該書的在線版本 – Paparazzi 2012-06-21 13:58:07

+0

這本書就在這裏。 http://msdn.microsoft.com/en-us/library/ff963553.aspx – 2012-07-04 05:24:54

2

簡短的回答是:不

內部,TPL使用標準ThreadPool安排其工作,這樣你實際上是在問是否。 ThreadPool考慮了機器負載,但它並沒有。限制同時運行的任務數量的唯一的事情是線程池中的線程數,沒有別的。

是否有可能讓外部進程報告回到你的應用程序,一旦他們準備好了?在這種情況下,你不必等待它們(保持線程佔用)。

-1

使用TPL/ThreadPool安排測試大量循環旋轉任務的測試。使用外部應用程序,我使用proc親和力將其中一個內核加載到100%。活動任務的數量從未減少。

更好的是,我運行了同一CPU密集型.NET TPL應用程序的多個實例。所有應用程序的線程數都是相同的,即使我的機器幾乎不可用,但永遠不會低於內核數量。所以理論上,TPL使用可用內核的數量,但從不檢查其實際負載。我認爲這是一個非常糟糕的實施。

+0

我認爲TPL在_minimum_分配的線程數等於核心數。它確實檢查負載,並可能增加線程數量,但不會減少低於最小值的線程數量。 – 2016-10-05 21:02:23

+0

這是正確的。這是最低限度的默認值。這是一個警察。您是否期望我們的開發人員創建可監控整體CPU負載的代碼(不包括我們正在運行的應用程序),並相應地減少線程池消耗的最小和最大線程數?或者我們應該手動將最小值從開始設置爲1?或者也許2?或CPU計數/ 2? 而btw不,它不監視實際的CPU負載。它只監視自己的線程。就這樣。 – MoonStom 2016-10-05 21:40:06

+0

發生在我身上的事情是,TPL很貪婪,它可能會爬到CPU超額訂閱的位置,如果這有利於任務的執行速度,所以你是正確的,TPL監視任務/秒而不是CPU負載,一些缺點。我的觀點是,我不認爲微軟希望開發人員在使用TPL時改變處理器親和力。 – 2016-10-05 22:24:05

相關問題