【原創】騰訊面試官:線程池要設置多大

【原創】騰訊面試官:線程池要設置多大

含淚播種的人必定能含笑收穫。

有個朋友Hunter跟我聊,最近他參加騰訊的面試,在二面的時候被問到了關於線程池線程數目設置的一個問題。此處記錄下這個問題的面試過程,以及後面關於此問題的理論方面的知識講解。面試

面試過程

面試官開場了:算法

線程池你用過吧,線程數是怎麼設置的呢?

Hunter心想,這不難啊,曾經在《Java併發編程》一書中有看到過線程池中線程數目設置的講述,因而張口就來:數據庫

線程數的設置須要考慮三方面的因素,服務器的配置、服務器資源的預算和任務自身的特性。具體來講就是服務器有多少個CPU,多少內存,IO支持的最大QPS是多少,任務主要執行的是計算、IO仍是一些混合操做,任務中是否包含數據庫鏈接等的稀缺資源。線程池的線程數設置主要取決於這些因素。

面試官追問來了:編程

那具體是怎麼設置呢?

Hunter略一思忖,整理了下思路,娓娓道來:服務器

假設機器有N個CPU,那麼對於計算密集型的任務,應該設置線程數爲N+1;對於IO密集型的任務,應該設置線程數爲2N;對於同時有計算工做和IO工做的任務,應該考慮使用兩個線程池,一個處理計算任務,一個處理IO任務,分別對兩個線程池按照計算密集型和IO密集型來設置線程數。

面試官表情毫無變化,接着發問:網絡

N+1和2N是怎麼來的?

Hunter張口就來:併發

是個經驗值。

面試官:學習

經驗值嗎?那爲何不是N+2或者N+3,而非得是N+1呢?

Hunter被駁得稍有點懵,腦子裏努力在回想學習過的那些技術點,竟一時語塞。測試

看得出來面試官略有不滿,因而提示道:spa

那假如在一個請求中,計算操做須要5ms,DB操做須要100ms,對於一臺8個CPU的服務器,怎麼設置線程數呢?

Hunter努力平復心情,緊接着最開始的思路,說到:

這是一個計算和IO混合型的任務,能夠將其分解爲兩個線程池來處理。一個線程池處理計算操做,設置N+1=9個線程,一個線程處理IO操做,設置2N=16個線程。

面試官:

若是一個任務同時包含了一個計算操做和DB操做呢,不能拆分怎麼設置?你能講一下具體的計算過程嗎?

Hunter略有點慌,內心不斷給本身暗示:這個問題不難不難。而後不斷回想看過的《Java併發編程實戰》和《Java虛擬機併發編程》中關於線程池設置的章節,並試圖將本身對這個問題的分析思路也表達出來。

首先這個任務總體上是一個IO密集型的任務。在處理一個請求的過程當中,總共耗時100+5=105ms,而其中只有5ms是用於計算操做的,CPU利用率爲5/(100+5)。使用線程池是爲了儘可能提升CPU的利用率,減小對CPU資源的浪費,假設以100%的CPU利用率來講,要達到100%的CPU利用率,對於一個CPU就要設置其利用率的倒數個數的線程數,也即1/(5/(100+5)),8個CPU的話就乘以8。那麼算下來的話,就是……168,對,這個線程池要設置168個線程數。

面試官表情略有緩和,嘴角微微一笑:

若是實際的任務差別較大,不一樣任務實際的CPU操做耗時和IO操做耗時有所不一樣,那麼怎麼設置線程數呢?

通過剛纔的分析過程,Hunter內心已經回憶起了這塊的知識點,已然不慌了。

那對全部任務的CPU操做耗時和IO操做耗時求個平均值就行了。

Hunter內心漸漸恢復了自信,大腦的利用率瞬間提升好幾十個百分點。

面試官輕輕「嗯」了一聲,表示承認。

那若是如今這個IO操做是DB操做,而DB的QPS上限是1000,這個線程池又該設置爲多大呢?

通過剛纔的心理調整,對問題完整的分析過程,以及面試官的略微承認,Hunter已經知道如何去更好地回答面試官的問題了。

按比例來減小就能夠了,按照以前的計算過程,能夠計算出來當線程數設置爲168的時候,DB操做的QPS爲,168 (1000/(100+5))=1600,若是如今DB的QPS最大爲1000,那麼對應的,最大隻能設置168(1000/1600)=105個線程。

面試官此次是真的滿意了,給這個回答給了一個正面的評價:

思路挺清晰的。那設置線程池的時候除了考慮這些,還須要考慮哪些內容呢?

Hunter此時已經徹底找回自信了,不懼任何問題。

除了考慮任務CPU操做耗時、IO操做耗時以外,還須要服務器的內存資源、硬盤資源、網絡帶寬等等的。

面試官點點頭,看起來Hunter已經得到了面試官的正式承認了。面試官告訴Hunter,表現不錯,等接下來的面試安排吧。

面試後總結

Hunter心裏異常激動,這真算是一次「死裏逃生」的經歷了。面試結束後,Hunter壓抑興奮,立刻去找到《Java併發編程實戰》和《Java虛擬機併發編程》兩本書,翻到對應的章節,想確認下本身的回答。
果真,壓力除了會形成緊張以外,也能提升大腦利用率。Hunter在調整狀態後的回答徹底正確。附上兩本書中對線程池設置的理論。

線程數的第一種計算方法

在《Java併發編程實踐》中,是這樣來計算線程池的線程數目的:

在一個基準負載下,使用 幾種不一樣大小的線程池運行你的應用程序,並觀察CPU利用率的水平。
給定下列定義:

Ncpu = CPU的數量
Ucpu = 目標CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待時間與計算時間的比率

爲保持處理器達到指望的使用率,最優的池的大小等於:
  Nthreads = Ncpu x Ucpu x (1 + W/C)

這種計算方式,咱們須要知道上面定義的幾個數值,才能計算出來線程池須要設置的線程數。其中,CPU數量是肯定的,CPU使用率是目標值也是肯定的,W/C也是能夠經過基準程序測試得出的。

線程數的第二種計算方法

而在《Java虛擬機併發編程》中,則是這樣來計算線程池的線程數目的:

線程數 = CPU可用核心數/(1 - 阻塞係數),其中阻塞係數的取值在0和1之間。
計算密集型任務的阻塞係數爲0,而IO密集型任務的阻塞係數則接近1。一個徹底阻塞的任務是註定要掛掉的,因此咱們無須擔憂阻塞係數會達到1。

這種計算方式,咱們須要知道CPU可用核心數和阻塞係數,才能計算出來線程池須要設置的線程數目。其中,CPU可用核心數是肯定的,阻塞係數能夠經過公式:阻塞係數=阻塞時間/(阻塞時間+計算時間),其實也就是上一種算法中的W/C的方式來計算,因此阻塞係數也是能夠經過基準程序計算得出的。

所謂的經驗值怎麼來的

那麼咱們再來看所謂的N+1與2N的經驗值的來源。
計算密集型應用
以第一種計算方式來看,對於計算密集型應用,假定等待時間趨近於0,是的CPU利用率達到100%,那麼線程數就是CPU核心數,那這個+1意義何在呢?
《Java併發編程實踐》這麼說:

計算密集型的線程剛好在某時由於發生一個頁錯誤或者因其餘緣由而暫停,恰好有一個「額外」的線程,能夠確保在這種狀況下CPU週期不會中斷工做。

因此N+1確實是一個經驗值。
IO密集型應用
一樣以第一種方式來看,對於IO密集型應用,假定全部的操做時間幾乎都是IO操做耗時,那麼W/C的值就爲1,那麼對應的線程數確實爲2N。

本文由微型公衆號【Dali王的技術博客】原創,掃碼關注獲取更多原創技術文章。
Dali王的技術博客
相關文章
相關標籤/搜索