簡介
線程池(英語: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;
}