面試問我,建立多少個線程合適?我該怎麼說

| 若是好看,請給個贊面試

  • 你有一個思想,我有一個思想,咱們交換後,一我的就有兩個思想
  • If you can NOT explain it simply, you do NOT understand it well enough

爲何要使用多線程?

防止併發編程出錯最好的辦法就是不寫併發程序

既然多線程編程容易出錯,爲何它還經久不衰呢?編程

A:那還用說,確定在某些方面有特長唄,好比你知道的【它很快,很是快】安全

我也很贊同這個答案,但說的不夠具體服務器

併發編程適用於什麼場景?

若是問你選擇多線程的緣由就是一個【快】字,面試也就不會出那麼多幺蛾子了。你有沒有問過你本身網絡

  1. 併發編程在全部場景下都是快的嗎?
  2. 知道它很快,何爲快?怎樣度量?

想知道這兩個問題的答案,咱們須要一個從【定性】到【定量】的分析過程多線程

使用多線程就是在正確的場景下經過設置正確個數的線程來最大化程序的運行速度(我感受你仍是啥也沒說)

將這句話翻譯到硬件級別就是要充分的利用 CPU 和 I/O 的利用率併發

兩個正確獲得保證,也就能達到最大化利用 CPU 和 I/O的目的了。最關鍵是,如何作到兩個【正確】?工具

在聊具體場景的時候,咱們必需要拿出咱們的專業性來。送你兩個名詞 buff 加成性能

  • CPU 密集型程序
  • I/O 密集型程序

CPU 密集型程序

一個完整請求,I/O操做能夠在很短期內完成, CPU還有不少運算要處理,也就是說 CPU 計算的比例佔很大一部分

假如咱們要計算 1+2+....100億 的總和,很明顯,這就是一個 CPU 密集型程序spa

在【單核】CPU下,若是咱們建立 4 個線程來分段計算,即:

  1. 線程1計算 [1,25億)
  2. ...... 以此類推
  3. 線程4計算 [75億,100億]

咱們來看下圖他們會發生什麼?

因爲是單核 CPU,全部線程都在等待 CPU 時間片。按照理想狀況來看,四個線程執行的時間總和與一個線程5獨自完成是相等的,實際上咱們還忽略了四個線程上下文切換的開銷

因此,單核CPU處理CPU密集型程序,這種狀況並不太適合使用多線程

此時若是在 4 核CPU下,一樣建立四個線程來分段計算,看看會發生什麼?

每一個線程都有 CPU 來運行,並不會發生等待 CPU 時間片的狀況,也沒有線程切換的開銷。理論狀況來看效率提高了 4 倍

因此,若是是多核CPU 處理 CPU 密集型程序,咱們徹底能夠最大化的利用 CPU 核心數,應用併發編程來提升效率

I/O密集型程序

與 CPU 密集型程序相對,一個完整請求,CPU運算操做完成以後還有不少 I/O 操做要作,也就是說 I/O 操做佔比很大部分

咱們都知道在進行 I/O 操做時,CPU是空閒狀態,因此咱們要最大化的利用 CPU,不能讓其是空閒狀態

一樣在單核 CPU 的狀況下:

從上圖中能夠看出,每一個線程都執行了相同長度的 CPU 耗時和 I/O 耗時,若是你將上面的圖多畫幾個週期,CPU操做耗時固定,將 I/O 操做耗時變爲 CPU 耗時的 3 倍,你會發現,CPU又有空閒了,這時你就能夠新建線程 4,來繼續最大化的利用 CPU。

綜上兩種狀況咱們能夠作出這樣的總結:

線程等待時間所佔比例越高,須要越多線程;線程CPU時間所佔比例越高,須要越少線程。

到這裏,相信你已經知道第一個【正確】使用多線程的場景了,那建立多少個線程是正確的呢?

建立多少個線程合適?

面試若是問到這個問題,這但是對你理論和實踐的統考。想徹底答對,你必需要【精通/精通/精通】小學算術

從上面知道,咱們有 CPU 密集型和 I/O 密集型兩個場景,不一樣的場景固然須要的線程數也就不同了

CPU 密集型程序建立多少個線程合適?

有些同窗早已經發現,對於 CPU 密集型來講,理論上 線程數量 = CPU 核數(邏輯) 就能夠了,可是實際上,數量通常會設置爲 CPU 核數(邏輯)+ 1, 爲何呢?

《Java併發編程實戰》這麼說:

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

因此對於CPU密集型程序, CPU 核數(邏輯)+ 1 個線程數是比較好的經驗值的緣由了

I/O密集型程序建立多少個線程合適?

上面已經讓你們按照圖多畫幾個週期(你能夠動手將I/O耗時與CPU耗時比例調大,好比6倍或7倍),這樣你就會獲得一個結論,對於 I/O 密集型程序:

最佳線程數 = (1/CPU利用率) = 1 + (I/O耗時/CPU耗時)

我這麼體貼,固然擔憂有些同窗不理解這個公式,咱們將上圖的比例手動帶入到上面的公式中:

這是一個CPU核心的最佳線程數,若是多個核心,那麼 I/O 密集型程序的最佳線程數就是:

最佳線程數 = CPU核心數 (1/CPU利用率) = CPU核心數 1 + (I/O耗時/CPU耗時)

說到這,有些同窗可能有疑問了,要計算 I/O 密集型程序,是要知道 CPU 利用率的,若是我不知道這些,那要怎樣給出一個初始值呢?

按照上面公式,假如幾乎全是 I/O耗時,因此純理論你就能夠說是 2N(N=CPU核數),固然也有說 2N + 1的,(我猜這個 1 也是 backup),沒有找到具體的推倒過程,在【併發編程實戰-8.2章節】截圖在此,你們有興趣的能夠本身看看

理論上來講,理論上來講,理論上來講,這樣就能達到 CPU 100% 的利用率

若是理論都好用,那就用不着實踐了,也就更不會有調優的事出現了。不過在初始階段,咱們確實能夠按照這個理論之做爲僞標準, 畢竟差也可能不會差太多,這樣調優也會更好一些

談完理論,我們說點實際的,公式我看懂了(定性階段結束),可是我有兩個疑問:

  1. 我怎麼知道具體的 I/O耗時和CPU耗時呢?
  2. 怎麼查看CPU利用率?

沒錯,咱們須要定量分析了

幸運的是,咱們並非第一個吃螃蟹的仔兒,其實有不少 APM (Application Performance Manager)工具能夠幫咱們獲得準確的數據,學會使用這類工具,也就能夠結合理論,在調優的過程獲得更優的線程個數了。我這裏簡單列舉幾個,具體使用哪個,具體應用還須要你本身去調研選擇,受篇幅限制,暫不展開討論了

  1. SkyWalking
  2. CAT
  3. zipkin

上面瞭解了基本的理論知識,那面試有可能問什麼?又可能會以怎樣的方式提問呢?

面試小問

小問一

假設要求一個系統的 TPS(Transaction Per Second 或者 Task Per Second)至少爲20,而後假設每一個Transaction由一個線程完成,繼續假設平均每一個線程處理一個Transaction的時間爲4s

如何設計線程個數,使得能夠在1s內處理完20個Transaction?

可是,可是,這是由於沒有考慮到CPU數目。家裏又沒礦,通常服務器的CPU核數爲16或者32,若是有80個線程,那麼確定會帶來太多沒必要要的線程上下文切換開銷(但願這句話你能夠主動說出來),這就須要調優了,來作到最佳 balance

小問二

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

若是不知道請拿三年級期末考試題從新作(今天晚自習留下來),答案是:

線程數 = 8 * (1 + 100/5) = 168 (個)

那若是DB的 QPS(Query Per Second)上限是1000,此時這個線程數又該設置爲多大呢?

一樣,這是沒有考慮 CPU 數目,接下來就又是細節調優的階段了

由於一次請求不只僅包括 CPU 和 I/O操做,具體的調優過程還要考慮內存資源,網絡等具體內容

增長 CPU 核數必定能解決問題嗎?

看到這,有些同窗可能會認爲,即使我算出了理論線程數,但實際CPU核數不夠,會帶來線程上下文切換的開銷,因此下一步就須要增長 CPU 核數,那咱們盲目的增長 CPU 核數就必定能解決問題嗎?

在講互斥鎖的內容是,我故意遺留了一個知識:

怎麼理解這個公式呢?

這個結論告訴咱們,假如咱們的串行率是 5%,那麼咱們不管採用什麼技術,最高也就只能提升 20 倍的性能。

如何簡單粗暴的理解串行百分比(其實均可以經過工具得出這個結果的)呢?來看個小 Tips:

Tips: 臨界區都是串行的,非臨界區都是並行的,用單線程執行臨界區的時間/用單線程執行(臨界區+非臨界區)的時間就是串行百分比

如今你應該理解我在講解 synchronized 關鍵字時所說的:

最小化臨界區範圍,由於臨界區的大小每每就是瓶頸問題的所在,不要像亂用try catch那樣一鍋端

總結

多線程不必定就比但線程高效,好比大名鼎鼎的 Redis (後面會分析),由於它是基於內存操做,這種狀況下,單線程能夠很高效的利用CPU。而多線程的使用場景通常時存在至關比例的I/O或網絡操做

另外,結合小學數學題,咱們已經瞭解瞭如何從定性到定量的分析的過程,在開始沒有任何數據以前,咱們可使用上文提到的經驗值做爲一個僞標準,其次就是結合實際來逐步的調優(綜合 CPU,內存,硬盤讀寫速度,網絡情況等)了

最後,盲目的增長 CPU 核數也不必定能解決咱們的問題,這就要求咱們嚴格的編寫併發程序代碼了

靈魂追問

  1. 咱們已經知道建立多少個線程合適了,爲何還要搞一個線程池出來?
  2. 建立一個線程都要作哪些事情?爲何說頻繁的建立線程開銷很大?
  3. 多線程一般要注意共享變量問題,爲何局部變量就沒有線程安全問題呢?
  4. ......

下一篇文章,咱們就來講說,你熟悉又陌生的線程池問題

參考

感謝前輩們總結的精華,本身所寫的併發系列好多都參考瞭如下資料

  • Java 併發編程實戰
  • Java 併發編程之美
  • 碼出高效
  • Java 併發編程的藝術
  • ......

日拱一兵 | 原創

相關文章
相關標籤/搜索