自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

別再糾結(jié)線程池大小線程數(shù)量了,沒(méi)有固定公式的

開(kāi)發(fā) 后端
一個(gè)CPU核心,單位時(shí)間內(nèi)只能執(zhí)行一個(gè)線程的指令 那么理論上,我一個(gè)線程只需要不停的執(zhí)行指令,就可以跑滿(mǎn)一個(gè)核心的利用率。

 可能很多人都看到過(guò)一個(gè)線程數(shù)設(shè)置的理論:

  •  CPU 密集型的程序 - 核心數(shù) + 1
  •  I/O 密集型的程序 - 核心數(shù) * 2

不會(huì)吧,不會(huì)吧,真的有人按照這個(gè)理論規(guī)劃線程數(shù)?

線程數(shù)和CPU利用率的小測(cè)試

拋開(kāi)一些操作系統(tǒng),計(jì)算機(jī)原理不談,說(shuō)一個(gè)基本的理論(不用糾結(jié)是否嚴(yán)謹(jǐn),只為好理解):一個(gè)CPU核心,單位時(shí)間內(nèi)只能執(zhí)行一個(gè)線程的指令 那么理論上,我一個(gè)線程只需要不停的執(zhí)行指令,就可以跑滿(mǎn)一個(gè)核心的利用率。

來(lái)寫(xiě)個(gè)死循環(huán)空跑的例子驗(yàn)證一下:

測(cè)試環(huán)境:AMD Ryzen 5 3600, 6 - Core, 12 - Threads 

  1. public class CPUUtilizationTest {  
  2.  public static void main(String[] args) {  
  3.   //死循環(huán),什么都不做  
  4.   while (true){  
  5.   }  
  6.  }  
  7.  
  8. 復(fù)制代碼 

運(yùn)行這個(gè)例子后,來(lái)看看現(xiàn)在CPU的利用率:

從圖上可以看到,我的3號(hào)核心利用率已經(jīng)被跑滿(mǎn)了

那基于上面的理論,我多開(kāi)幾個(gè)線程試試呢? 

  1. public class CPUUtilizationTest {  
  2.  public static void main(String[] args) {  
  3.   for (int j = 0; j < 6; j++) {  
  4.    new Thread(new Runnable() {  
  5.     @Override  
  6.     public void run() {  
  7.      while (true){  
  8.      }  
  9.     }  
  10.    }).start();  
  11.   }  
  12.  }  
  13.  
  14. 復(fù)制代碼 

此時(shí)再看CPU利用率,1/2/5/7/9/11 幾個(gè)核心的利用率已經(jīng)被跑滿(mǎn):

那如果開(kāi)12個(gè)線程呢,是不是會(huì)把所有核心的利用率都跑滿(mǎn)?答案一定是會(huì)的:

如果此時(shí)我把上面例子的線程數(shù)繼續(xù)增加到24個(gè)線程,會(huì)出現(xiàn)什么結(jié)果呢?

從上圖可以看到,CPU利用率和上一步一樣,還是所有核心100%,不過(guò)此時(shí)負(fù)載已經(jīng)從11.x增加到了22.x(load average解釋參考scoutapm.com/blog/unders…),說(shuō)明此時(shí)CPU更繁忙,線程的任務(wù)無(wú)法及時(shí)執(zhí)行。

現(xiàn)代CPU基本都是多核心的,比如我這里測(cè)試用的AMD 3600,6核心12線程(超線程),我們可以簡(jiǎn)單的認(rèn)為它就是12核心CPU。那么我這個(gè)CPU就可以同時(shí)做12件事,互不打擾。

如果要執(zhí)行的線程大于核心數(shù),那么就需要通過(guò)操作系統(tǒng)的調(diào)度了。操作系統(tǒng)給每個(gè)線程分配CPU時(shí)間片資源,然后不停的切換,從而實(shí)現(xiàn)“并行”執(zhí)行的效果。

但是這樣真的更快嗎?從上面的例子可以看出,一個(gè)線程就可以把一個(gè)核心的利用率跑滿(mǎn)。如果每個(gè)線程都很“霸道”,不停的執(zhí)行指令,不給CPU空閑的時(shí)間,并且同時(shí)執(zhí)行的線程數(shù)大于CPU的核心數(shù),就會(huì)導(dǎo)致操作系統(tǒng)更頻繁的執(zhí)行切換線程執(zhí)行,以確保每個(gè)線程都可以得到執(zhí)行。

不過(guò)切換是有代價(jià)的,每次切換會(huì)伴隨著寄存器數(shù)據(jù)更新,內(nèi)存頁(yè)表更新等操作。雖然一次切換的代價(jià)和I/O操作比起來(lái)微不足道,但如果線程過(guò)多,線程切換的過(guò)于頻繁,甚至在單位時(shí)間內(nèi)切換的耗時(shí)已經(jīng)大于程序執(zhí)行的時(shí)間,就會(huì)導(dǎo)致CPU資源過(guò)多的浪費(fèi)在上下文切換上,而不是在執(zhí)行程序,得不償失。

上面死循環(huán)空跑的例子,有點(diǎn)過(guò)于極端了,正常情況下不太可能有這種程序。

大多程序在運(yùn)行時(shí)都會(huì)有一些 I/O操作,可能是讀寫(xiě)文件,網(wǎng)絡(luò)收發(fā)報(bào)文等,這些 I/O 操作在進(jìn)行時(shí)時(shí)需要等待反饋的。比如網(wǎng)絡(luò)讀寫(xiě)時(shí),需要等待報(bào)文發(fā)送或者接收到,在這個(gè)等待過(guò)程中,線程是等待狀態(tài),CPU沒(méi)有工作。此時(shí)操作系統(tǒng)就會(huì)調(diào)度CPU去執(zhí)行其他線程的指令,這樣就完美利用了CPU這段空閑期,提高了CPU的利用率。

上面的例子中,程序不停的循環(huán)什么都不做,CPU要不停的執(zhí)行指令,幾乎沒(méi)有啥空閑的時(shí)間。如果插入一段I/O操作呢,I/O 操作期間 CPU是空閑狀態(tài),CPU的利用率會(huì)怎么樣呢?先看看單線程下的結(jié)果: 

  1. public class CPUUtilizationTest {  
  2.  public static void main(String[] args) throws InterruptedException {  
  3.   for (int n = 0; n < 1; n++) {  
  4.    new Thread(new Runnable() {  
  5.     @Override  
  6.     public void run() {  
  7.      while (true){  
  8.                         //每次空循環(huán) 1億 次后,sleep 50ms,模擬 I/O等待、切換  
  9.       for (int i = 0; i < 100_000_000l; i++) {   
  10.       }  
  11.       try {  
  12.        Thread.sleep(50);  
  13.       }  
  14.       catch (InterruptedException e) {  
  15.        e.printStackTrace();  
  16.       }  
  17.      }  
  18.     }  
  19.    }).start();  
  20.   }  
  21.  }  
  22.  
  23. 復(fù)制代碼 

哇,唯一有利用率的9號(hào)核心,利用率也才50%,和前面沒(méi)有sleep的100%相比,已經(jīng)低了一半了。現(xiàn)在把線程數(shù)調(diào)整到12個(gè)看看:

單個(gè)核心的利用率60左右,和剛才的單線程結(jié)果差距不大,還沒(méi)有把CPU利用率跑滿(mǎn),現(xiàn)在將線程數(shù)增加到18:

此時(shí)單核心利用率,已經(jīng)接近100%了。由此可見(jiàn),當(dāng)線程中有 I/O 等操作不占用CPU資源時(shí),操作系統(tǒng)可以調(diào)度CPU可以同時(shí)執(zhí)行更多的線程。

現(xiàn)在將I/O事件的頻率調(diào)高看看呢,把循環(huán)次數(shù)減到一半,50_000_000,同樣是18個(gè)線程:

此時(shí)每個(gè)核心的利用率,大概只有70%左右了。

線程數(shù)和CPU利用率的小總結(jié)

上面的例子,只是輔助,為了更好的理解線程數(shù)/程序行為/CPU狀態(tài)的關(guān)系,來(lái)簡(jiǎn)單總結(jié)一下:

  •  一個(gè)極端的線程(不停執(zhí)行“計(jì)算”型操作時(shí)),就可以把單個(gè)核心的利用率跑滿(mǎn),多核心CPU最多只能同時(shí)執(zhí)行等于核心數(shù)的“極端”線程數(shù)
  •  如果每個(gè)線程都這么“極端”,且同時(shí)執(zhí)行的線程數(shù)超過(guò)核心數(shù),會(huì)導(dǎo)致不必要的切換,造成負(fù)載過(guò)高,只會(huì)讓執(zhí)行更慢
  •  I/O 等暫停類(lèi)操作時(shí),CPU處于空閑狀態(tài),操作系統(tǒng)調(diào)度CPU執(zhí)行其他線程,可以提高CPU利用率,同時(shí)執(zhí)行更多的線程
  •  I/O 事件的頻率頻率越高,或者等待/暫停時(shí)間越長(zhǎng),CPU的空閑時(shí)間也就更長(zhǎng),利用率越低,操作系統(tǒng)可以調(diào)度CPU執(zhí)行更多的線程

線程數(shù)規(guī)劃的公式

前面的鋪墊,都是為了幫助理解,現(xiàn)在來(lái)看看書(shū)本上的定義?!禞ava 并發(fā)編程實(shí)戰(zhàn)》介紹了一個(gè)線程數(shù)計(jì)算的公式:

公式很清晰,現(xiàn)在來(lái)帶入上面的例子試試看:

如果我期望目標(biāo)利用率為90%(多核90),那么需要的線程數(shù)為:

核心數(shù)12 * 利用率0.9 * (1 + 50(sleep時(shí)間)/50(循環(huán)50_000_000耗時(shí))) ≈ 22

現(xiàn)在把線程數(shù)調(diào)到22,看看結(jié)果:

現(xiàn)在CPU利用率大概80+,和預(yù)期比較接近了,由于線程數(shù)過(guò)多,還有些上下文切換的開(kāi)銷(xiāo),再加上測(cè)試用例不夠嚴(yán)謹(jǐn),所以實(shí)際利用率低一些也正常。

把公式變個(gè)形,還可以通過(guò)線程數(shù)來(lái)計(jì)算CPU利用率:

Ucpu=

線程數(shù)22 / (核心數(shù)12 * (1 + 50(sleep時(shí)間)/50(循環(huán)50_000_000耗時(shí)))) ≈ 0.9

雖然公式很好,但在真實(shí)的程序中,一般很難獲得準(zhǔn)確的等待時(shí)間和計(jì)算時(shí)間,因?yàn)槌绦蚝軓?fù)雜,不只是“計(jì)算”。一段代碼中會(huì)有很多的內(nèi)存讀寫(xiě),計(jì)算,I/O 等復(fù)合操作,精確的獲取這兩個(gè)指標(biāo)很難,所以光靠公式計(jì)算線程數(shù)過(guò)于理想化。

真實(shí)程序中的線程數(shù)

那么在實(shí)際的程序中,或者說(shuō)一些Java的業(yè)務(wù)系統(tǒng)中,線程數(shù)(線程池大?。┮?guī)劃多少合適呢?

先說(shuō)結(jié)論:沒(méi)有固定答案,先設(shè)定預(yù)期,比如我期望的CPU利用率在多少,負(fù)載在多少,GC頻率多少之類(lèi)的指標(biāo)后,再通過(guò)測(cè)試不斷的調(diào)整到一個(gè)合理的線程數(shù)

比如一個(gè)普通的,SpringBoot 為基礎(chǔ)的業(yè)務(wù)系統(tǒng),默認(rèn)Tomcat容器+HikariCP連接池+G1回收器,如果此時(shí)項(xiàng)目中也需要一個(gè)業(yè)務(wù)場(chǎng)景的多線程(或者線程池)來(lái)異步/并行執(zhí)行業(yè)務(wù)流程。

此時(shí)我按照上面的公式來(lái)規(guī)劃線程數(shù)的話,誤差一定會(huì)很大。因?yàn)榇藭r(shí)這臺(tái)主機(jī)上,已經(jīng)有很多運(yùn)行中的線程了,Tomcat有自己的線程池,HikariCP也有自己的后臺(tái)線程,JVM也有一些編譯的線程,連G1都有自己的后臺(tái)線程。這些線程也是運(yùn)行在當(dāng)前進(jìn)程、當(dāng)前主機(jī)上的,也會(huì)占用CPU的資源。

所以受環(huán)境干擾下,單靠公式很難準(zhǔn)確的規(guī)劃線程數(shù),一定要通過(guò)測(cè)試來(lái)驗(yàn)證。

流程一般是這樣:

    1. 分析當(dāng)前主機(jī)上,有沒(méi)有其他進(jìn)程干擾

    2. 分析當(dāng)前JVM進(jìn)程上,有沒(méi)有其他運(yùn)行中或可能運(yùn)行的線程

    3. 設(shè)定目標(biāo)

        a. 目標(biāo)CPU利用率 - 我最高能容忍我的CPU飆到多少?

        b. 目標(biāo)GC頻率/暫停時(shí)間 - 多線程執(zhí)行后,GC頻率會(huì)增高,最大能容忍到什么頻率,每次暫停時(shí)間多少?

        c. 執(zhí)行效率 - 比如批處理時(shí),我單位時(shí)間內(nèi)要開(kāi)多少線程才能及時(shí)處理完畢

        d. ……

    4. 梳理鏈路關(guān)鍵點(diǎn),是否有卡脖子的點(diǎn),因?yàn)槿绻€程數(shù)過(guò)多,鏈路上某些節(jié)點(diǎn)資源有限可能會(huì)導(dǎo)致大量的線程在等待資源(比如三方接口限流,連接池?cái)?shù)量有限,中間件壓力過(guò)大無(wú)法支撐等)

    5. 不斷的增加/減少線程數(shù)來(lái)測(cè)試,按最高的要求去測(cè)試,最終獲得一個(gè)“滿(mǎn)足要求”的線程數(shù)**

而且而且而且!不同場(chǎng)景下的線程數(shù)理念也有所不同:

  1.  Tomcat中的maxThreads,在Blocking I/O和No-Blocking I/O下就不一樣
  2.  Dubbo 默認(rèn)還是單連接呢,也有I/O線程(池)和業(yè)務(wù)線程(池)的區(qū)分,I/O線程一般不是瓶頸,所以不必太多,但業(yè)務(wù)線程很容易稱(chēng)為瓶頸
  3.  Redis 6.0以后也是多線程了,不過(guò)它只是I/O 多線程,“業(yè)務(wù)”處理還是單線程

所以,不要糾結(jié)設(shè)置多少線程了。沒(méi)有標(biāo)準(zhǔn)答案,一定要結(jié)合場(chǎng)景,帶著目標(biāo),通過(guò)測(cè)試去找到一個(gè)最合適的線程數(shù)。

可能還有同學(xué)可能會(huì)有疑問(wèn):“我們系統(tǒng)也沒(méi)啥壓力,不需要那么合適的線程數(shù),只是一個(gè)簡(jiǎn)單的異步場(chǎng)景,不影響系統(tǒng)其他功能就可以”

很正常,很多的內(nèi)部業(yè)務(wù)系統(tǒng),并不需要啥性能,穩(wěn)定好用符合需求就可以了。那么我的推薦的線程數(shù)是:CPU核心數(shù)

附錄

Java 獲取CPU核心數(shù) 

  1. Runtime.getRuntime().availableProcessors()//獲取邏輯核心數(shù),如6核心12線程,那么返回的是12  
  2. 復(fù)制代碼 

Linux 獲取CPU核心數(shù) 

  1. # 總核數(shù) = 物理CPU個(gè)數(shù) X 每顆物理CPU的核數(shù)   
  2. # 總邏輯CPU數(shù) = 物理CPU個(gè)數(shù) X 每顆物理CPU的核數(shù) X 超線程數(shù)  
  3. # 查看物理CPU個(gè)數(shù)  
  4. cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l  
  5. # 查看每個(gè)物理CPU中core的個(gè)數(shù)(即核數(shù))  
  6. cat /proc/cpuinfo| grep "cpu cores"| uniq  
  7. # 查看邏輯CPU的個(gè)數(shù)  
  8. cat /proc/cpuinfo| grep "processor"| wc -l  
  9. 復(fù)制代碼 

 

責(zé)任編輯:龐桂玉 來(lái)源: 數(shù)據(jù)庫(kù)開(kāi)發(fā)
相關(guān)推薦

2010-03-18 15:15:08

Java線程池

2024-10-21 18:12:14

2019-09-09 09:50:27

設(shè)置Java線程池

2012-05-15 02:18:31

Java線程池

2012-06-14 10:21:31

線程線程池Java

2025-01-03 08:40:53

Java并發(fā)編程Guava庫(kù)

2023-05-19 08:01:24

Key消費(fèi)場(chǎng)景

2021-09-11 15:26:23

Java多線程線程池

2023-06-07 13:49:00

多線程編程C#

2019-09-26 10:19:27

設(shè)計(jì)電腦Java

2023-11-22 08:37:40

Java線程池

2024-07-15 08:20:24

2022-12-07 10:56:23

線程池監(jiān)控執(zhí)行超時(shí)

2020-12-10 08:24:40

線程池線程方法

2022-02-10 11:43:54

DUBBO線程池QPS

2021-04-18 07:12:08

Dubbo線程池

2023-09-19 14:59:47

線程開(kāi)發(fā)

2023-10-13 08:20:02

Spring線程池id

2021-06-24 08:02:35

線程池Java代碼

2021-06-17 06:57:10

SpringBoot線程池設(shè)置
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)