學習執行緒時,我們描述了一個多執行緒的 Web 伺服器,每當伺服器接收到一個請求時,它都會建立一個單獨執行緒來處理請求。雖然建立一個單獨執行緒肯定優於建立一個單獨進程,但是多執行緒伺服器仍然有些潛在的問題。
第一個問題是建立執行緒所需的時間多少,以及執行緒在完成工作之後會被丟棄的事實。第二個問題更為麻煩,如果允許所有並行請求都通過新執行緒來處理,那麼我們沒有限制系統內的並行執行執行緒的數量。無限制的執行緒可能耗盡系統資源,如 CPU 時間和記憶體。解決這個問題的一種方法是使用執行緒池。
執行緒池的主要思想是:在進程開始時建立一定數量的執行緒,並加到池中以等待工作。當伺服器收到請求時,它會喚醒池內的一個執行緒(如果有可用執行緒),並將需要服務的請求傳遞給它。一旦執行緒完成了服務,它會返回到池中再等待工作。如果池內沒有可用執行緒,那麼伺服器會等待,直到有空執行緒為止。
執行緒池具有以下優點:
-
用現有執行緒服務請求比等待建立一個執行緒更快。
-
執行緒池限制了任何時候可用執行緒的數量。這對那些不能支援大量並行執行緒的系統非常重要。
-
將要執行任務從建立任務的機制中分離出來,允許我們採用不同策略執行任務。例如,任務可以被安排在某一個時間延遲後執行,或定期執行。
池內執行緒的數量可以通過一些因素來加以估算,如系統 CPU 的數量、實體記憶體的大小和並行客戶請求數量的期望值等。更為高階的執行緒池架構可以根據使用模式動態調整池內執行緒數量。這類架構在系統負荷低時,提供了較小的池,從而減低記憶體消耗(如 Apple的大中央排程)。
Windows API 提供了與執行緒池有關的多個函數。使用執行緒池 API 類似於通過函數 Thread_Create() 來建立執行緒,參見《執行緒庫》一節。這裡,定義一個函數,以作為單獨執行緒來執行。這樣一個函數如下所示:
DWORD WINAPI PoolFunction(AVOID Param) {
/*
* this function runs as a separate thread.
*/
}
PoolFunction() 的指標會被傳給執行緒池 API 中的一個函數,池內的某個執行緒會執行該函數。執行緒池 API 的一個這種函數為 QueueUserWorkItem(),它需要三個引數:
-
LPTHREAD START ROUTINE Function:作為一個單獨執行緒來執行的函數的指標。
-
PVOID Param:傳遞給 Function 的引數。
-
ULONG Flags:標誌,用於指示多執行緒池如何建立執行緒和管理執行緒執行。
呼叫這個函數的範例如下:
QueueUserWorkItem(&PoolFunction, NULL, 0);
這讓執行緒池的一個執行緒代替程式設計師來呼叫 PoolFunction()。這裡,我們沒有傳遞引數給 PoolFunction()。由於標誌設為 0,因此針對執行緒池如何建立執行緒,沒有特殊指令。
Windows 執行緒池 API 還包括可週期性地呼叫函數,或在一個非同步 I/O 請求結束時呼叫函數。Java API 的
java.util.concurrent
也提供了執行緒池支援。