雲栖号資訊:【 點選檢視更多行業資訊】
在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
為什麼要使用多線程?
防止并發程式設計出錯最好的辦法就是不寫并發程式

既然多線程程式設計容易出錯,為什麼它還經久不衰呢?
A:那還用說,肯定在某些方面有特長呗,比如你知道的【它很快,非常快】
我也很贊同這個答案,但說的不夠具體
并發程式設計适用于什麼場景?
如果問你選擇多線程的原因就是一個【快】字,面試也就不會出那麼多幺蛾子了。你有沒有問過你自己
1.并發程式設計在所有場景下都是快的嗎?
2.知道它很快,何為快?怎樣度量?
想知道這兩個問題的答案,我們需要一個從【定性】到【定量】的分析過程
使用多線程就是在正确的場景下通過設定正确個數的線程來最大化程式的運作速度(我感覺你還是啥也沒說)
将這句話翻譯到硬體級别就是要充分的利用 CPU 和 I/O 的使用率
兩個正确得到保證,也就能達到最大化利用 CPU 和 I/O的目的了。最關鍵是,如何做到兩個【正确】?
在聊具體場景的時候,我們必須要拿出我們的專業性來。送你兩個名詞 buff 加成
- CPU 密集型程式
- I/O 密集型程式
一個完整請求,I/O操作可以在很短時間内完成, CPU還有很多運算要處理,也就是說 CPU 計算的比例占很大一部分
假如我們要計算 1+2+....100億 的總和,很明顯,這就是一個 CPU 密集型程式
在【單核】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 核數也不一定能解決我們的問題,這就要求我們嚴格的編寫并發程式代碼了。
【雲栖号線上課堂】每天都有産品技術專家分享!
課程位址:
https://yqh.aliyun.com/zhibo立即加入社群,與專家面對面,及時了解課程最新動态!
【雲栖号線上課堂 社群】
https://c.tb.cn/F3.Z8gvnK
原文釋出時間:2020-04-08
本文作者:日拱一兵
本文來自:“
掘金”,了解相關資訊可以關注“掘金”