執行緒與並行模型

執行緒池(thread pool)

想像一家計程車公司。服務乘客最浪費的方式,是為每一趟車程都全新地雇用、訓練、再解雇一位司機。明智的方式是讓一隊司機待命;有人叫車時,一位閒著的司機接下,車程結束後司機回來等下一趟。執行緒池就是程式裡那支待命車隊:一組固定的工作執行緒,事先一次建立好,坐在那裡等著被從共享佇列裡分派任務。

具體來說,程式啟動時建立例如八條工作執行緒和一個佇列。每條工作執行緒不斷迴圈:從佇列裡取一個任務(若佇列空就等待),把它執行到完成,再回頭取下一個。當程式有工作要做(處理這個請求、處理那個檔案)時,它不建立執行緒,只是把任務丟進佇列,下一個空閒的工作執行緒就會撿起來做。工作執行緒的數量通常會依機器調校(常設在接近 CPU 核心數),讓系統保持忙碌又不至於被壓垮。

它勝過「每任務一執行緒」的原因有兩個。第一,建立與銷毀執行緒並非免費,尤其是核心層級執行緒,每次都要進出核心一趟,所以重複利用一組固定的執行緒可以把那個成本攤平掉。第二,執行緒池限制了同時執行的執行緒數量,避免大量湧入的任務產生上千條執行緒、把機器拖垮。誠實的提醒是:若固定大小的執行緒池中的工作執行緒全都阻塞在等某樣東西(例如一個緩慢的資料庫),這個池就可能變成瓶頸,因為阻塞中的工作執行緒並沒有在做有用的工作,而佇列只會不斷變長。

一個使用執行緒池的網頁伺服器例如備有 16 條工作執行緒。當一千個請求同時湧入時,它不會產生一千條執行緒,而是把請求排進佇列,由那 16 條工作執行緒一次 16 個地把它們處理完。伺服器保持快速而穩定,而不會在上千條全新的執行緒之下崩潰。

把工作執行緒事先建好一次、永遠重複使用,更便宜也有上限。

固定大小的執行緒池刻意為並行設了上限,這通常是好事,但若每條工作執行緒都阻塞在某樣很慢的東西上,整個池就會停擺,任務佇列只會越積越多。大小與阻塞行為必須一併考量。

又稱
worker poolpool of worker threads工作執行緒池線程池