如今是討論線程池內應該有多少線程的恰當時機。從兩個方面考慮。首先,在服務應用程序初始化時,要建立最少的線程集而沒必要以正常方法建立和銷燬。記住,建立和銷燬線程會浪費CPU時間,所以進程的線程數量越小越好。其次,線程的數量要有一個最大值,建立過多的線程會浪費系統資源。即便大部部分資源能夠被對換出內存,最小化對系統資源的使用以及不浪費頁面空間仍然頗有優點,若是可以作到的話。算法
你可能想體會一下不一樣數量的線程的結果。大多數服務(包括微軟的IIS)使用啓發式算法來管理它們的線程池。建議你也這麼作。例如,能夠如下的變量來管理線程池。架構
LONG g_nThreadsMin; // 線程池內線程的最小數量 LONG g_nThreadsMax; // 線程池內線程的最大數量 LONG g_nThreadsCrnt; //當前線程池內的線程數量 LONG g_nThreadsBusy; // 線程池內正在幹活的線程數量
在應用程序初始化時,建立等於g_nThreadsMin的數量的線程,這些線程都執行相同線程池函數。如下僞代碼展現了一個線程池函數:併發
DWORD WINAPI ThreadPoolFunc(PVOID pv) { // 線程正在進入線程池 InterlockedIncrement(&g_nThreadsCrnt); InterlockedIncrement(&g_nThreadsBusy); for (BOOL fStayInPool = TRUE; fStayInPool;) { // 線程中止執行並等待工做 InterlockedDecrement(&m_nThreadsBusy); BOOL fOk = GetQueuedCompletionStatus(...); DWORD dwIOError = GetLastError(); // 線程有工做要作,所以處於忙狀態 int nThreadsBusy = InterlockedIncrement(&m_nThreadsBusy); // 應該加另外一個線程到線程池中麼? if (nThreadsBusy == m_nThreadsCrnt) { // 全部的線程都在忙 if (nThreadsBusy < m_nThreadsMax) { // 線程池沒有滿 if (GetCPUUsage() < 75) { // CPU 使用率小於75% // 向池中添加線程 CloseHandle(chBEGINTHREADEX(...)); } } } if (!fOk && (dwIOError == WAIT_TIMEOUT)) { // 線程超時 if (!ThreadHasIoPending()) { // 沒有太多的服務要作,所以線程能夠結束,由於沒有未完成的I/O請求 fStayInPool = FALSE; } } if (fOk || (po != NULL)) { // 線程被喚醒以進行處理,開始進行處理 ... if (GetCPUUsage() > 90) { // CPU 使用率超過 90% if (!ThreadHasIoPending()) { // 沒有阻塞的I/O請求 if (g_nThreadsCrnt > g_nThreadsMin)) { // 當前線程數量大於最小值 fStayInPool = FALSE; // 從池中刪除線程 } } } } } // 線程離開線程池 InterlockedDecrement(&g_nThreadsBusy); InterlockedDecrement(&g_nThreadsCurrent); return(0); }
這段僞碼展現了使用完成端口時能夠有多大的創造性。GetCPUUsage和ThreadHasIoPending函數並非Windows API。若是想使用它們,必須本身實現。另外,必須保證線程池內至少老是有一個線程,不然客戶端將永遠被擋在門外。能夠上面的僞碼爲參考,不過特定的服務應用程序若是使用不一樣的架構可能會有更好的效果。函數
注意工具
在本章的前面,小節「取消設備隊列中的I/O請求」,曾說過系統會在線程終止時自動取消該線程產生的全部被阻塞的I/O請求。這就是爲何僞碼中像ThreadHasIoPending這樣函數必需存在。若是線程仍有未完成的I/O請求,不要終止線程。
許多服務提供一個管理工具來容許管理員控制線程池的行爲,例如,設置最大和最小線程數量,CPU使用率閾值,還有建立完成端口時指定的最大併發值。線程