如何選擇線程池最優線程數

任務分類

我們一般用一個線程池做同一種類型的任務,而不是把各種類型的任務都丟進同一個線程池執行。

而任務可以分成2種類型:CPU 密集型、IO密集型。

公式

先來看看2個公式,這兩個公式適用任何一種類型。

公式一

Nthreads = Ncpu x Ucpu x (1 + W/C)

其中:
Ncpu = CPU的核心數量
Ucpu = CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待時間與計算時間的比率

注意: Intel 引入超線程技術後,使核心數與線程數形成1:2的關係。1個CPU核心2個邏輯核心。
這裏的 Ncpu,指邏輯核心數。在 Java 裏,可以用如下代碼獲得邏輯核心數:
Runtime.getRuntime().availableProcessors();

公式二

Nthreads = Ncpu /(1 - 阻塞係數)

對比

我們希望這兩個公式其實是互通的。假設公式一種的 CPU 使用率爲1,即CPU可以100%運轉。令公式一等於公式二,得 Ncpu x (1 + W/C) = Ncpu /(1 - 阻塞係數),推導得:

阻塞係數 = W / (W + C),即 阻塞係數 = 阻塞時間 /(阻塞時間 + 計算時間)

可以發現,這2公式其實是從不同的角度描述同一個東西。

聯想

我們重點看公式一,因爲 W/C 比 W / (W + C),更簡潔。並且仍然不考慮 CPU 的使用率,假設它100%運轉,因爲本來就是要充分利用 CPU 嘛。

Nthreads = Ncpu x (1 + W/C)

W/C 我們現在可以理解爲阻塞時間 / 計算時間

這裏有2種情況值得討論:

  1. W = 0,這種情況CPU幾乎沒有阻塞的時候,一直在計算。這其實就是CPU密集型
  2. W > C,這種情況阻塞時間比CPU計算時間還長,一般出現這種情況的,都是因爲有IO,用戶空間總是要等待內核空間準備好數據;甚至是網絡IO,還要等待數據在網絡中的傳輸。這其實就是IO密集型

CPU密集型

對於這種類型,通過上面的分析,其實已經有答案了:

Nthreads = Ncpu,即線程數等於邏輯核心數。

拋開公式理解,爲什麼是邏輯核心數:
邏輯核心數決定了同時最多有多少個線程工作。線程數大於邏輯核心數,由於時間片輪轉機制,肯定會有上下文的切換,從而浪費了CPU的性能。

但還有一種說法:
一個有Ncpu個處理器的系統通常通過使用一個Ncpu + 1個線程的線程池來獲得最優的利用率(計算密集型的線程恰好在某時因爲發生一個頁錯誤或者因其他原因而暫停,剛好有一個“額外”的線程,可以確保在這種情況下CPU週期不會中斷工作)。

但是這種做法導致的多一個線程,上下文切換是否值得,需要自己考量。個人不建議這一種。

IO密集型

對於這種類型,關鍵在於 W > C,W 取多少合適。
這個需要測試,從 W = C 開始,即 Nthreads = 2Ncpu。線程數不會是越多越好,因爲每一個線程都要佔用一定的資源。

如果不想測試,那麼就用 Nthreads = 2Ncpu 就好了,一般也都是這麼設置的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章