線程池

多線程處理形式

線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程后自動啟動這些任務。線程池線程都是後台線程。每個線程都使用默認的堆棧大小,以默認的優先順序運行,並處於多線程單元中。如果某個線程在託管代碼中空閑(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙。如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間后創建另一個輔助線程但線程的數目永遠不會超過最大值。超過最大值的線程可以排隊,但他們要等到其他線程完成後才啟動。

簡介


線程池(英語:thread pool):一種線程使用模式。線程過多會帶來調度開銷,進而影響緩存局部性和整體性能。而線程池維護著多個線程,等待著監督管理者分配可併發執行的任務。這避免了在處理短時間任務時創建與銷毀線程的代價。線程池不僅能夠保證內核的充分利用,還能防止過分調度。可用線程數量應該取決於可用的併發處理器、處理器內核、內存、網路sockets等的數量。例如,線程數一般取cpu數量+2比較合適,線程數過多會導致額外的線程切換開銷。
任務調度以執行線程的常見方法是使用同步隊列,稱作任務隊列。池中的線程等待隊列中的任務,並把執行完的任務放入完成隊列中。
線程池模式一般分為兩種:HS/HA半同步/半非同步模式、L/F領導者與跟隨者模式。
● 半同步/半非同步模式又稱為生產者消費者模式,是比較常見的實現方式,比較簡單。分為同步層、隊列層、非同步層三層。同步層的主線程處理工作任務並存入工作隊列,工作線程從工作隊列取出任務進行處理,如果工作隊列為空,則取不到任務的工作線程進入掛起狀態。由於線程間有數據通信,因此不適於大數據量交換的場合。
● 領導者跟隨者模式,在線程池中的線程可處在3種狀態之一:領導者leader、追隨者follower或工作者processor。任何時刻線程池只有一個領導者線程。事件到達時,領導者線程負責消息分離,並從處於追隨者線程中選出一個來當繼任領導者,然後將自身設置為工作者狀態去處置該事件。處理完畢后工作者線程將自身的狀態置為追隨者。這一模式實現複雜,但避免了線程間交換任務數據,提高了CPU cache相似性。在ACE(Adaptive Communication Environment)中,提供了領導者跟隨者模式實現。
線程池的伸縮性對性能有較大的影響。
● 創建太多線程,將會浪費一定的資源,有些線程未被充分使用。
● 銷毀太多線程,將導致之後浪費時間再次創建它們。
● 創建線程太慢,將會導致長時間的等待,性能變差。
● 銷毀線程太慢,導致其它線程資源飢餓。

組成部分


伺服器程序利用線程技術響應客戶請求已經司空見慣,可能您認為這樣做效率已經很高,但您有沒有想過優化一下使用線程的方法。該文章將向您介紹伺服器程序如何利用線程池來優化性能並提供一個簡單的線程池實現。
1、線程池管理器(ThreadPoolManager):用於創建並管理線程池
2、工作線程(WorkThread): 線程池中線程
3、任務介面(Task):每個任務必須實現的介面,以供工作線程調度任務的執行。
4、任務隊列:用於存放沒有處理的任務。提供一種緩衝機制。

技術背景


在面向對象編程中,創建和銷毀對象是很費時間的,因為創建一個對象要獲取內存資源或者其它更多資源。在Java中更是如此,虛擬機將試圖跟蹤每一個對象,以便能夠在對象銷毀後進行垃圾回收。所以提高服務程序效率的一個手段就是儘可能減少創建和銷毀對象的次數,特別是一些很耗資源的對象創建和銷毀。如何利用已有對象來服務就是一個需要解決的關鍵問題,其實這就是一些"池化資源"技術產生的原因。比如大家所熟悉的資料庫連接池正是遵循這一思想而產生的,本文將介紹的線程池技術同樣符合這一思想。
目前,一些著名的大公司都特別看好這項技術,並早已經在他們的產品中應用該技術。比如IBM的WebSphere,IONA的Orbix 2000在SUN的 Jini中,Microsoft的MTS(Microsoft Transaction Server 2.0),COM+等。

功能


應用程序可以有多個線程,這些線程在休眠狀態中需要耗費大量時間來等待事件發生。其他線程可能進入睡眠狀態,並且僅定期被喚醒以輪循更改或更新狀態信息,然後再次進入休眠狀態。為了簡化對這些線程的管理,.NET框架為每個進程提供了一個線程池,一個線程池有若干個等待操作狀態,當一個等待操作完成時,線程池中的輔助線程會執行回調函數。線程池中的線程由系統管理,程序員不需要費力於線程管理,可以集中精力處理應用程序任務。

傳遞參數


調用QueueUserWorkItem時傳入的Object類型參數傳遞到任務過程,可以通過這種方式來向任務過程傳遞參數。如果任務過程需要多個參數,可以定義包含這些數據的類,並將其強制轉換為Object數據類型。

應用範圍


1、需要大量的線程來完成任務,且完成任務的時間比較短。 WEB伺服器完成網頁請求這樣的任務,使用線程池技術是非常合適的。因為單個任務小,而任務數量巨大,你可以想象一個熱門網站的點擊次數。但對於長時間的任務,比如一個Telnet連接請求,線程池的優點就不明顯了。因為Telnet會話時間比線程的創建時間大多了。
2、對性能要求苛刻的應用,比如要求伺服器迅速響應客戶請求。
3、接受突發性的大量請求,但不至於使伺服器因此產生大量線程的應用。突發性大量客戶請求,在沒有線程池情況下,將產生大量線程,雖然理論上大部分操作系統線程數目最大值不是問題,短時間內產生大量線程可能使內存到達極限,並出現"OutOfMemory"的錯誤。

示例


線程池
//線程池示例using System;using System.Threading;public classTest{ //存放要計算的數值的欄位 static double number1 = -1; static double number2 = -1; public static void Main() { //獲取線程池的最大線程數和維護的最小空閑線程數 int maxThreadNum, minThreadNum; int portThreadNum; ThreadPool.GetMaxThreads(out maxThreadNum, out portThreadNum); ThreadPool.GetMinThreads(out minThreadNum, out portThreadNum); Console.WriteLine("最大線程數:{0}", maxThreadNum); Console.WriteLine("最小線程數:{0}", minThreadNum); //函數變數值 int x=15600; //啟動第一個任務:計算x的8次方 Console.WriteLine("啟動第一個任務:計算{0}的8次方。", x); ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc1), x); //啟動第二個任務:計算x的8次方根 Console.WriteLine("啟動第二個任務:計算{0}的8次方根。", x); ThreadPool.QueueUserWorkItem(new WaitCallback(TaskProc2), x); //等待,直到兩個數值都完成計算 while(number1 == -1 || number2 == -1); //列印計算結果 Console.WriteLine("y({0}) = {1}", x, number1 + number2); Console.Read(); } //啟動第一個任務:計算x的8次方 static void TaskProc1(object o) { number1 = Math.Pow(Convert.ToDouble(o), 8); } //啟動第二個任務:計算x的8次方根 static void TaskProc2(object o) { number2 = Math.Pow(Convert.ToDouble(o), 1.0/8.0); }}

池結構

[HostProtection(SecurityAction.LinkDemand,Synchronization=true,ExternalThreading=true)]public static class ThreadPool{[Obsolete("ThreadPool.BindHandle(IntPtr)hasbeendeprecated.PleaseuseThreadPool.BindHandle(SafeHandle)instead.",false),SecurityPermission(SecurityAction.Demand,Flags=SecurityPermissionFlag.UnmanagedCode)]public static bool BindHandle(IntPtr osHandle){ if(osHandle == null){throw new ArgumentNullException("osHandle");} bool flag = false; bool success = false; RuntimeHelpers.PrepareConstrainedRegions(); try { osHandle.DangerousAddRef(ref success); flag = BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); } finally { if(success) osHandle.DangerousRelease(); } return flag;}