工做線程數究竟要設置爲多少

【轉載於58同城沈劍】程序員

 1、需求緣起數據庫

Web-Server一般有個配置,最大工做線程數,後端服務通常也有個配置,工做線程池的線程數量,這個線程數的配置不一樣的業務架構師有不一樣的經驗值,有些業務設置爲CPU核數的2倍,有些業務設置爲CPU核數的8倍,有些業務設置爲CPU核數的32倍。編程

 

「工做線程數」的設置依據是什麼,到底設置爲多少可以最大化CPU性能,是本文要討論的問題。後端

 

2、共性認知

在進行進一步深刻討論以前,先以提問的方式就一些共性認知達成一致。tomcat

 

問:工做線程數是否是設置的越大越好?服務器

答:確定不是的網絡

  • 服務器CPU核數有限,可以同時併發的線程數有限,單核CPU設置10000個工做線程沒有意義多線程

  • 線程切換是有開銷的,若是線程切換過於頻繁,反而會使性能下降架構

 

問:調用sleep()函數的時候,線程是否一直佔用CPU併發

答:不佔用,等待時會把CPU讓出來,給其餘須要CPU資源的線程使用。

 

不止sleep()函數,在進行一些阻塞調用時,例如網絡編程中的:

  • 阻塞accept(),等待客戶端鏈接

  • 阻塞recv(),等待下游回包

都不佔用CPU資源。

 

問:單核CPU,設置多線程有意義麼,是否能提升併發性能?

答:即便是單核,使用多線程也是有意義的,大多數狀況也能提升併發

  • 多線程編碼可讓代碼更加清晰,例如:IO線程收發包,Worker線程進行任務處理,Timeout線程進行超時檢測

  • 若是有一個任務一直佔用CPU資源在進行計算,此時增長線程並不能增長併發,例如如下代碼會一直佔用CPU,並使得CPU佔用率達到100%:

     while(1){ i++; }

  • 一般來講,Worker線程通常不會一直佔用CPU進行計算,此時即便CPU是單核,增長Worker線程也可以提升併發,由於這個線程在休息的時候,其餘的線程能夠繼續工做

 

3、常見服務線程模型

瞭解常見的服務線程模型,有助於理解服務併發的原理,通常來講互聯網常見的服務線程模型有兩種:

  • IO線程與工做現場經過任務隊列解耦

  • 純異步

 

IO線程與工做線程經過隊列解耦類模型

 


如上圖,大部分Web-Server與服務框架都是使用這樣的一種「IO線程與Worker線程經過隊列解耦」類線程模型:

  • 有少數幾個IO線程監聽上游發過來的請求,並進行收發包(生產者)

  • 有一個或者多個任務隊列,做爲IO線程與Worker線程異步解耦的數據傳輸通道(臨界資源)

  • 有多個工做線程執行正真的任務(消費者)

 

這個線程模型應用很廣,符合大部分場景,這個線程模型的特色是,工做線程內部是同步阻塞執行任務的(回想一下tomcat線程中是怎麼執行Java程序的,dubbo工做線程中是怎麼執行任務的),所以能夠經過增長Worker線程數來增長併發能力,今天要討論的重點是「該模型Worker線程數設置爲多少能達到最大的併發」。

 

純異步線程模型

沒有阻塞,這種線程模型只須要設置不多的線程數就可以作到很高的吞吐量,該模型的缺點是:

  • 若是使用單線程模式,難以利用多CPU多核的優點

  • 程序員更習慣寫同步代碼,callback的方式對代碼的可讀性有衝擊,對程序員的要求也更高

  • 框架更復雜,每每須要server端收發組件,server端隊列,client端收發組件,client端隊列,上下文管理組件,有限狀態機組件,超時管理組件的支持

 

文章《RPC-client異步收發核心細節?》中有更詳細的介紹,however,這個模型不是今天討論的重點,

 

4、工做線程的工做模式

瞭解工做線程的工做模式,對量化分析線程數的設置很是有幫助:

 

上圖是一個典型的工做線程的處理過程,從開始處理start到結束處理end,該任務的處理共有7個步驟:

  • 從工做隊列裏拿出任務,進行一些本地初始化計算,例如http協議分析、參數解析、參數校驗等

  • 訪問cache拿一些數據

  • 拿到cache裏的數據後,再進行一些本地計算,這些計算和業務邏輯相關

  • 經過RPC調用下游service再拿一些數據,或者讓下游service去處理一些相關的任務

  • RPC調用結束後,再進行一些本地計算,怎麼計算和業務邏輯相關

  • 訪問DB進行一些數據操做

  • 操做完數據庫以後作一些收尾工做,一樣這些收尾工做也是本地計算,和業務邏輯相關

 

分析整個處理的時間軸,會發現:

  • 其中1,3,5,7步驟中(上圖中粉色時間軸),線程進行本地業務邏輯計算時須要佔用CPU

  • 而2,4,6步驟中(上圖中橙色時間軸),訪問cache、service、DB過程當中線程處於一個等待結果的狀態,不須要佔用CPU,進一步的分解,這個「等待結果」的時間共分爲三部分:

    2.1)請求在網絡上傳輸到下游的cache、service、DB

    2.2)下游cache、service、DB進行任務處理

    2.3)cache、service、DB將報文在網絡上傳回工做線程

 

5、量化分析併合理設置工做線程數

最後一塊兒來回答工做線程數設置爲多少合理的問題。

 

經過上面的分析,Worker線程在執行的過程當中,有一部計算時間須要佔用CPU,另外一部分等待時間不須要佔用CPU,經過量化分析,例如打日誌進行統計,能夠統計出整個Worker線程執行過程當中這兩部分時間的比例,例如:

  • 執行計算,佔用CPU的時間(粉色時間軸)是100ms

  • 等待時間,不佔用CPU的時間(橙色時間軸)也是100ms

 

獲得的結果是,這個線程計算和等待的時間是1:1,即有50%的時間在計算(佔用CPU),50%的時間在等待(不佔用CPU):

  • 假設此時是單核,則設置爲2個工做線程就能夠把CPU充分利用起來,讓CPU跑到100%

  • 假設此時是N核,則設置爲2N個工做現場就能夠把CPU充分利用起來,讓CPU跑到N*100%

 

結論

N核服務器,經過執行業務的單線程分析出本地計算時間爲x,等待時間爲y,則工做線程數(線程池線程數)設置爲 N*(x+y)/x,能讓CPU的利用率最大化。

 

經驗

通常來講,非CPU密集型的業務(加解密、壓縮解壓縮、搜索排序等業務是CPU密集型的業務),瓶頸都在後端數據庫訪問或者RPC調用,本地CPU計算的時間不多,因此設置幾十或者幾百個工做線程是可以提高吞吐量的。

 

6、總結

    • 線程數不是越多越好

    • sleep()不佔用CPU

    • 單核設置多線程不但能使得代碼清晰,還能提升吞吐量

    • 站點和服務最經常使用的線程模型是「IO線程與工做現場經過任務隊列解耦」,此時設置多工做線程能夠提高吞吐量

    • N核服務器,經過日誌分析出任務執行過程當中,本地計算時間爲x等待時間爲y,則工做線程數(線程池線程數)設置爲 N*(x+y)/x,能讓CPU的利用率最大化

相關文章
相關標籤/搜索