CLR 的線程池

發表于:2007-05-25來源:作者:點擊數: 標簽:線程Jeffrey日期CLR發布
發布日期: 1/14/2005 | 更新日期: 1/14/2005 Jeffrey Richter Microsoft 一直試圖提高其平臺與應用程序的 性能 。許多年前,Microsoft 研究了應用程序 開發 人員是如何使用線程的,以便看看能做些什么來提高他們的效用。這項研究有一個很重要的發現:開發
發布日期: 1/14/2005 | 更新日期: 1/14/2005

*

Microsoft 一直試圖提高其平臺與應用程序的性能。許多年前,Microsoft 研究了應用程序開發人員是如何使用線程的,以便看看能做些什么來提高他們的效用。這項研究有一個很重要的發現:開發人員經常創建新線程來執行一項任務,當該項任務完成時,線程終止。

這種模式在服務器應用程序中極其常見??蛻舳苏埱?a href='http://www.anti-gravitydesign.com/ceshi/ruanjianceshikaifajishu/rjcshj' target='_blank'>服務器,服務器創建一個線程來處理客戶端的請求,然后當完成客戶端的請求時,該服務器的線程終止。與進程相比較,創建和銷毀線程的速度更快,使用的操作系統資源更少。但創建和銷毀線程當然不是免費的。

要 創建一個線程,需要分配和初始化一個內核對象,也需要分配和初始化線程的堆??臻g,而且 Windows® 為進程中的每個 DLL 發送一個 DLL_THREAD_ATTACH 通知,使磁盤中的頁分配到內存中,從而執行代碼。當線程終止時,給每個 DLL 都發送一個 DLL_THREAD_DETACH 通知,線程的堆??臻g被釋放,內核對象亦被釋放(如果其使用數達到 0)。因此,與創建和銷毀線程相關的許多開銷都和創建線程原本要執行的工作無關。

線程池的產生

這項研究結果促使 Microsoft 去實現線程池,線程池最早出現在 Windows 2000 中。當 Microsoft® .NET Framework 小組設計并構建公共語言運行庫 (CLR) 時,他們決定就在 CLR 自身中實現線程池。這樣,即使應用程序是在 Windows 2000 以前的 Windows 版本(例如 Windows 98)中運行,任何托管的應用程序也都能利用線程池。

當 CLR 初始化時,其線程池中不含有線程。當應用程序要創建線程來執行任務時,該應用程序應請求線程池線程來執行任務。線程池知道后將創建一個初始線程。該新線程 經歷的初始化和其他線程一樣;但是任務完成后,該線程不會自行銷毀。相反,它會以掛起狀態返回線程池。如果應用程序再次向線程池發出請求,那么這個掛起的 線程將激活并執行任務,而不會創建新線程。這節約了很多開銷。只要線程池中應用程序任務的排隊速度低于一個線程處理每項任務的速度,那么就可以反復重用同 一線程,從而在應用程序生存期內節約大量開銷。

那么,如果線程池中應用程序任務排隊的速度超過一個線程處理任務的速度,則線程池將創建額外的線程。當然,創建新線程確實會產生額外開銷,但應用程序在其生存期中很可能只請求幾個線程來處理交給它的所有任務。因此,總體來說,通過使用線程池可以提高應用程序的性能。

現 在您可能想知道,如果線程池包含許多線程而應用程序的工作負荷又在減少,將會發生什么事情。這種情況下,線程池包含幾個長期掛起的線程,浪費著操作系統的 資源。Microsoft 也考慮到了這個問題。當線程池線程自身掛起時,它等待 40 秒鐘。如果 40 秒過去后線程無事可做,則該線程將激活并自行銷毀,釋放出它使用的全部操作系統資源(堆棧、內核對象,等等)。同時,激活并自行銷毀線程可能并不影響應用 程序的性能,因為應用程序做的事情畢竟不是太多,否則就會恢復執行該線程。順便說一句,盡管我說線程池中的線程是在 40 秒內自行激活的,但實際上這個時間并沒有驗證并可以改變。

線程池的一個絕妙特性是:它是啟發式的。如果您的應用程序需要執行很多任務,那么線程池將創建更多的線程。如果您的應用程序的工作負載逐漸減少,那么線程池線程將自行終止。線程池的算法確保它僅包含置于其上的工作負荷所需要的線程數!

因此,希望您現在已理解了線程池的基本概念,并明白了它所能提供的性能優勢?,F在我將給出一些代碼來說明如何使用線程池。首先,您應該知道線程池可以提供四種功能:

異步調用方法

以一定的時間間隔調用方法

當單個內核對象得到信號通知時調用方法

當異步 I/O 請求結束時調用方法

前三種功能非常有用,我將在本專欄中加以說明。而應用程序開發人員很少使用第四種功能,因此在此我將不做說明;有可能在將來的專欄中講到。

功能 1:異步調用方法

在您的應用程序中,如果有創建新線程來執行任務的代碼,那么我建議您用命令線程池執行該任務的新代碼來替換它。事實上,您通常會發現,讓線程池執行任務比讓一個新的專用線程來執行任務更容易。

要 排隊線程池任務,您可以使用 System.Threading 命名空間中定義的 ThreadPool 類。ThreadPool 類只提供靜態方法,且不能構造它的實例。要讓線程池線程異步調用方法,您的代碼必須調用一個 ThreadPool 的重載 QueueUserWorkItem 方法,如下所示:

public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
public static Boolean QueueUserWorkItem(WaitCallback wc);

這些方法將“工作項”(和可選狀態數據)排隊到線程池的線程中,并立即返回。工作項只是一種方法(由 wc 參數標識),它被調用并傳遞給單個參數,即狀態(狀態數據)。沒有狀態參數的 QueueUserWorkItem 版本將 null 傳遞給回調方法。最后,池中的某些線程將調用您的方法來處理該工作項。您編寫的回調方法必須與 System.Threading.WaitCallback 委托類型相匹配,其定義如下:

public delegate void WaitCallback(Object state);

請注意,永遠不要調用任何可以自己創建線程的方法;如果需要,CLR 的線程池將自動創建線程,如果可能還將重用現有的線程。另外,線程處理回調方法后不會立即銷毀該線程;它將返回到線程池并準備處理隊列中的其他工作項。使 用 QueueUserWorkItem 會使您的應用程序更有效,因為您將不需要為每個客戶端請求創建和銷毀線程。

圖 1 中的代碼說明了如何讓線程池異步調用一個方法。

功能 2:以一定的時間間隔調用方法

如 果您的應用程序需要在某一時間執行某項任務,或者您的應用程序需要定期執行某些方法,那么使用線程池將是您的最佳選擇。System.Threading 命名空間定義 Timer 類。當您構造 Timer 類的實例時,您是在告訴線程池您想在將來的某個特定時間回調自己的某個方法。Timer 類有四種構造函數:

public Timer(TimerCallback callback, Object state,
Int32 dueTime, Int32 period);
public Timer(TimerCallback callback, Object state,
UInt32 dueTime, UInt32 period);
public Timer(TimerCallback callback, Object state,
Int64 dueTime, Int64 period);
public Timer(TimerCallback callback, Object state,
Timespan dueTime, TimeSpan period);

所有這四種構造函數構造完全相同的 Timer 對象?;卣{參數標識您想由線程池線程回調的方法。當然,您編寫的回調方法必須與 System.Threading.TimerCallback 委托類型相匹配,其定義如下:

public delegate void TimerCallback(Object state);

構造函數的狀態參數允許您將狀態數據傳遞給回調方法;如果沒有要傳遞的狀態數據,您可以傳遞 null。使用 dueTime 參數可以告訴線程池在第一次調用您的回調方法之前需要等待多少毫秒??梢岳靡粋€有符號或無符號的 32 位值、一個有符號的 64 位值,或者一個 TimeSpan 值來指定毫秒數。如果您想立即調用回調方法,那么請將 dueTime 參數指定為 0。最后一個參數 period 允許您指定在每次連續調用之前需要等待的時間,單位為毫秒。如果您將 0 傳遞給這個參數,那么線程池將僅調用該回調方法一次。

構造 Timer 對象后,線程池知道要做什么,并自動為您監視時間。然而,Timer 類還提供了幾種其他的方法,允許您與線程池進行通信,以便更改什么時候(或者是否)應當回調方法。具體地說,Timer 類提供了幾種 Change 和 Dispose 方法:

public Boolean Change(Int32    dueTime, Int32    period);
public Boolean Change(UInt32 dueTime, UInt32 period);
public Boolean Change(Int64 dueTime, Int64 period);
public Boolean Change(TimeSpan dueTime, TimeSpan period);
public Boolean Dispose();
public Boolean Dispose(WaitHandle notifyObject);

Change 方法允許您更改 Timer 對象的 dueTime 和 period。Dispose 方法允許您在所有掛起的回調已經完成的時候,完全取消回調,并可選地用信號通知由 notifyObject 參數標識的內核對象。

圖 2 中的代碼說明如何讓線程池線程立即調用一個方法,并且每隔 2000 毫秒(或兩秒)再次調用。

功能 3:當單個內核對象得到信號通知時調用方法

Microsoft 研究人員在做性能研究時發現,許多應用程序生成線程,只是為了等待某單個內核對象得到信號通知。一旦該對象得到信號通知,這個線程就將某種通知發送給另一 個線程,然后環回,等待該對象再次發出信號。有些開發人員編寫的代碼中甚至有幾個線程,而每個線程都在等待一個對象。這是系統資源的巨大浪費。因此,如果 當前您的應用程序中有多個線程在等待單個內核對象得到信號通知,那么線程池仍將是您提高應用程序性能的最佳資源。

要讓線程池線程在內核對象 得到信號通知時調用您的回調方法,您可以再次利用 System.Threading.ThreadPool 類中定義的一些靜態方法。要讓線程池線程在內核對象得到信號通知時調用方法,您的代碼必須調用一個重載的 RegisterWaitHandle 方法,可以參見圖 3。

當 您調用這些方法之一時,h 參數標識出您想要線程池等待的內核對象。由于該參數是抽象基類 System.Threading.WaitHandle,因此您可以指定從該基類派生出來的任何類。特別地,您可以將一個引用傳遞給 AutoResetEvent、ManualResetEvent 或 Mutex object。第二個參數 callback 標識出您想要線程池線程調用的方法。您實現的回調方法必須與 System.Threading.WaitOrTimerCallback 委托類型相匹配,其定義如下列代碼行所示:

public delegate void WaitOrTimerCallback(Object state,
Boolean timedOut);

第三個參數 state 允許您指定應傳遞給回調方法的某些狀態數據,如果沒有特別的狀態數據要傳遞,則傳遞 null。第四個參數 milliseconds 允許您告訴線程池內核對象得到信號通知前應該等待的時間。這里通常傳遞 -1,以表示無限超時。如果最后一個參數 executeOnlyOnce 為真,那么線程池線程將僅執行回調方法一次。但是,如果 executeOnlyOnce 為假,那么線程池線程將在內核對象每次得到信號通知時執行回調方法。這對 AutoResetEvent 對象非常有用。

當調用回調方法 時,會傳遞給它狀態數據和 Boolean 值 timedOut。如果 timedOut 為假,則該方法知道它被調用的原因是內核對象得到信號通知。如果 timedOut 為真,則該方法知道它被調用的原因是內核對象在指定時間內沒有得到信號通知?;卣{方法應該執行所有必需的操作。

在前面所示的原型中,您會注 意到 RegisterWaitForSingleObject 方法返回一個 RegisteredWaitHandle 對象。該對象確定線程池在等待的內核對象。如果由于某種原因,您的應用程序要告訴線程池停止監視已注冊的等待句柄,那么您的應用程序就可以調用 RegisteredWaitHandle 的 Unregister 方法:

public Boolean Unregister(WaitHandle waitObject);

waitObject 參數表明當執行完隊列中的所有工作項后,您想如何得到信號通知。如果不想得到信號通知,那么您應將 null 傳遞給該參數。如果您將一個有效引用傳遞給 WaitHandle-derived 對象,那么線程池會在已注冊等待句柄的所有掛起工作項執行完后,通知該對象。

圖 4 中的代碼說明如何讓線程池線程在內核對象得到信號通知時調用方法。

小結

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97