1 線程池的概念
許多應用程序使用多個線程,但這些線程經常在休眠狀態中耗費大量的時間來等待事件發生。其他線程可能進入休眠狀態,並且僅定期被喚醒以輪詢更改或更新狀態信息,然后再次進入休眠狀態。為了簡化對這些線程的管理,.NET框架為每一個進程提供了一個線程池,使應用程序能夠根據需要來有效地利用多個線程。一個線程監視排到線程池的若干個等待操作的狀態。當一個等待操作完成時,線程池中的一個輔助線程就會執行對應的回調函數。線程池中的線程由系統進行管理,程序員不需要費力於線程管理,可以集中精力處理應用程序任務。
線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然后在創建線程后自動啟動這些任務。線程池線程都是后台線程。每個線程都使用默認堆棧大小,以默認的優先級運行,並處於多線程單元中。如果某個線程在托管代碼中空閑(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間之后創建另一個輔助線程。但線程的數目永遠不會超過最大值。超過最大值的其他線程可以排隊,但它們要等到其他線程完成后才啟動。
2 線程池的應用范圍
線程池特別適合於執行一些需要多個線程的任務。使用線程池能夠優化這些任務的執行過程,從而提高吞吐量,它不僅能夠使系統針對此進程優化該執行過程,而且還能夠使系統針對計算機上的其他進程優化該執行過程。如果需要啟動多個不同的任務,而不想分別設置每個線程的屬性,則可以使用線程池。
如果應用程序需要對線程進行特定的控制,則不適合使用線程池,需要創建並管理自己的線程。
在以下幾種情況下,適合於使用線程池線程:
(1)不需要前台執行的線程。
(2)不需要在使用線程具有特定的優先級。
(3)線程的執行時間不易過長,否則會使線程阻塞。由於線程池具有最大線程數限制,因此大量阻塞的線程池線程可能會阻止任務啟動。
(4)不需要將線程放入單線程單元。所有 ThreadPool 線程均不處於多線程單元中。
(5)不需要具有與線程關聯的穩定標識,或使某一線程專用於某一任務。
3 ThreadPool類
在多線程的程序中,經常會出現兩種情況:一種是在應用程序中,線程把大部分的時間花費在等待狀態,等待某個事件發生,然后才能給予響應,這一般使用ThreadPool(線程池)來解決;另一種情況是在線程平時都處於休眠狀態,只是周期性地被喚醒,這一般使用Timer(定時器)來解決。下面對ThreadPool類進行詳細說明。
ThreadPool類提供一個線程池,該線程池可用於發送工作項、處理異步 I/O、代表其他線程等待以及處理計時器。該類提供一個由系統維護的線程池(可以看作一個線程的容器),該容器需要Windows 2000以上系統支持,因為其中某些方法調用了只有高版本的Windows才有的API函數。
下面介紹一下該類所提供的方法,如表1所示。
表1 ThreadPool類的方法
方法 |
描述 |
BindHandle |
將操作系統句柄綁定到ThreadPool |
GetAvailableThreads |
檢索由GetMaxThreads方法返回的最大線程池線程數和當前活動線程數之間的差值 |
GetMaxThreads |
檢索可以同時處於活動狀態的線程池請求的數目。所有大於此數目的請求將保持排隊狀態,直到線程池線程變為可用 |
GetMinThreads |
檢索線程池在新請求預測中維護的空閑線程數 |
QueueUserWorkItem |
將方法排入隊列以便執行。此方法在有線程池線程變得可用時執行 |
RegisterWaitForSingleObject |
注冊正在等待WaitHandle的委托 |
SetMaxThreads |
設置可以同時處於活動狀態的線程池的請求數目。所有大於此數目的請求將保持排隊狀態,直到線程池線程變為可用 |
SetMinThreads |
設置線程池在新請求預測中維護的空閑線程數 |
UnsafeQueueNativeOverlapped |
將重疊的 I/O 操作排隊以便執行 |
UnsafeQueueUserWorkItem |
注冊一個等待 WaitHandle 的委托 |
UnsafeRegisterWaitForSingleObject |
將指定的委托排隊到線程池 |
通過以上方法,可以對線程池進行設置以及相應的操作,那么,我們什么時候使用線程池呢?我們在用Thread類調用線程時,一次只能使用一個線程來創建和刪除線程,這種方式的建立和刪除線程對CPU的使用是很頻繁的,為了節省CPU的負荷,可以使用線程池對線程進行操作,為了使讀者更深入的了解Thread類與ThreadPool類的差別。
在以下情況下,應使用ThreadPool類:
- 要以最簡單的方式創建和刪除線程;
- 應用程序使用線程的性能要優先考慮。
- 在以下情況下,應使用Thread類:
- 要控制所創建線程的優先級;
- 希望所使用的線程維護其標識,該標識要與線程一起進行各種操作,經過許多不同的時間段;
- 所使用的線程的壽命較長。
ThreadPool類會在線程的托管池中重用已有的線程。使用完線程后,線程就會返回線程池,供以后使用。ThreadPool有25個可用的線程(每個處理器)。
在使用線程池時,一般調用ThreadPool類的QueueUserWorkItem方法。
用戶並不需要自已建立線程,只需要把要進行的操作寫成函數,然后作為參數傳遞給ThreadPool類的QueueUserWorkItem方法就行了,傳遞的方法是依靠WaitCallback代理對象,而線程的建立、管理、運行等工作都是由系統自動完成的,用戶無須考慮那些復雜的細節問題。ThreadPool類的用法:首先程序創建了一個ManualResetEvent對象,該對象就像一個信號燈,可以利用它的信號來通知其它線程。
4 線程池的設置
為了讀者能更好的控制線程池,下面講解一下如何設置和讀取線程池的最大線程數和最小空閑線程數。
1.線程池的最大線程數
可排隊到線程池的操作數僅受可用內存的限制;但是,線程池限制進程中可以同時處於活動狀態的線程數。默認情況下,限制每個CPU可以使用25個輔助線程和1000 個I/O完成線程。
通過使用GetMaxThreads和SetMaxThreads方法可以控制最大線程數。下面對這兩個方法的聲明進行一下說明。
(1)GetMaxThreads方法
該方法檢索可以同時處於活動狀態的線程池請求的數目。所有大於此數目的請求將保持排隊狀態,直到線程池線程變為可用。其語法如下:
public static void GetMaxThreads(out int workerThreads,out int completionPortThreads)
參數說明:
workerThreads:Int32,線程池中輔助線程的最大數目。
CompletionPortThreads:Int32,線程池中異步I/O線程的最大數目。
(2)SetMaxThreads方法
該方法設置可以同時處於活動狀態的線程池的請求數目。所有大於此數目的請求將保持排隊狀態,直到線程池線程變為可用。
[SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)] public static bool SetMaxThreads(int workerThreads,int completionPortThreads)
參數說明:
workerThreads:Int32,線程池中輔助線程的最大數目。
CompletionPortThreads:Int32,線程池中異步 I/O 線程的最大數目。
返回值:bool型,如果更改成功,則為true;否則為false。
下面通過一段代碼對GetMaxThreads和SetMaxThreads方法的應用進行一下講解,主要是顯示線程池中輔助線程的最大數和線程池中異步I/O線程的最大數,以及用戶自行設置后的線程池情況。代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { //設置正在等待線程的事件為終止 AutoResetEvent autoEvent = new AutoResetEvent(false); int workerThreads; int portThreads; //獲取處於活動狀態的線程池請求的數目 ThreadPool.GetMaxThreads(out workerThreads, out portThreads); //在控制台中顯示處於活動狀態的線程池請求的數目 Console.WriteLine("設置前,線程池中輔助線程的最大數為:" + workerThreads.ToString() + ";線程池中異步I/O線程的最大數為:" + portThreads.ToString()); workerThreads = 10;//設置輔助線程的最大數 portThreads = 500;//設置線程池中異步I/O線程的最大數 //設置處於活動狀態的線程池請求的數目 ThreadPool.SetMaxThreads(workerThreads, portThreads); ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadMethod), autoEvent);//執行線程池 //在控制台中顯示設置后的處於活動狀態的線程池請求的數目 Console.WriteLine("設置后,線程池中輔助線程的最大數為:" + workerThreads.ToString() + ";線程池中異步I/O線程的最大數為:" + portThreads.ToString()); Console.ReadLine(); } static void ThreadMethod(object stateInfo) { Console.WriteLine("執行線程池"); } } }
運行結果如下:
注意:在 .NET Framework 1.0 和 1.1 版中,不能從托管代碼中設置線程池大小。承載公共語言運行庫的代碼可以使用 mscoree.h 中定義的 CorSetMaxThreads 設置該大小。
2.線程池的最小空閑線程數
即使是在所有線程都處於空閑狀態時,線程池也會維持最小的可用線程數,以便隊列任務可以立即啟動。將終止超過此最小數目的空閑線程,以節省系統資源。默認情況下,每個處理器維持一個空閑線程。
在啟動新的空閑線程之前,線程池具有一個內置延遲(在 .NET Framework 2.0 版中為半秒鍾)。應用程序在短期內定期啟動許多任務時,空閑線程數的微小增加會導致吞吐量顯著增加。將空閑線程數設置得過高會浪費系統資源。
使用GetMinThreads和SetMinThreads方法可以控制線程池所維持的空閑線程數。下面對這兩個方法進行一下說明。
(1)GetMinThreads方法
該方法用於檢索線程池在新請求預測中維護的空閑線程數。其語法如下:
public static void GetMinThreads(out int workerThreads,out int completionPortThreads)
參數說明:
workerThreads:Int32,當前由線程池維護的空閑輔助線程的最小數目。
CompletionPortThreads:Int32,當前由線程池維護的空閑異步I/O線程的最小數目。
(2)SetMinThreads方法
該方法用於設置線程池在新請求預測中維護的空閑線程數。其語法如下:
[SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)] public static bool SetMinThreads(int workerThreads,int completionPortThreads)
參數說明:
workerThreads:類型:Int32,要由線程池維護的新的最小空閑輔助線程數。
CompletionPortThreads:Int32,要由線程池維護的新的最小空閑異步 I/O 線程數。
返回值:bool型,如果更改成功,則為 true;否則為 false。
下面用一段代碼來詳細說明一下GetMinThreads和SetMinThreads方法的應用,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int minWorker, minIOC; //獲取線程池在新請求預測中維護的默認空閑線程數 ThreadPool.GetMinThreads(out minWorker, out minIOC); Console.WriteLine("設置前,線程池維護的空閑輔助線程的最小數目為:" + minWorker.ToString() + ";線程池維護的空閑異步I/O線程的最小數目為:" + minIOC.ToString());//在控制台中顯示線程池的默認空閑線程數 minWorker = 4;//設置線程池維護的空閑輔助線程的最小數 minIOC = 10;//設置線程池維護的空閑異步I/O線程的最小數 if (ThreadPool.SetMinThreads(minWorker, minIOC))//如果更改成功 { Console.WriteLine("設置后,線程池維護的空閑輔助線程的最小數目為:" + minWorker.ToString() + ";線程池維護的空閑異步I/O線程的最小數目為:" + minIOC.ToString());//在控制台中顯示更改后的線程池的默認空閑線程數 } else { Console.WriteLine("沒有設置"); } Console.ReadLine(); } } }
運行結果如下:
注意:在.NET Framework 1.0版中,不能設置最小空閑線程數。