C++線程池ThreadPoolExecutor實現(xiàn)原理

1. 為什么要使用線程池

在實際使用中古胆,線程是很占用系統(tǒng)資源的甩骏,如果對線程管理不善很容易導致系統(tǒng)問題揖庄。因此,在大多數(shù)并發(fā)框架中都會使用線程池來管理線程溪猿,使用線程池管理線程主要有如下好處:

降低資源消耗钩杰。通過復用已存在的線程和降低線程關閉的次數(shù)來盡可能降低系統(tǒng)性能損耗;

提升系統(tǒng)響應速度诊县。通過復用線程讲弄,省去創(chuàng)建線程的過程,因此整體上提升了系統(tǒng)的響應速度依痊;

提高線程的可管理性避除。線程是稀缺資源怎披,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源瓶摆,還會降低系統(tǒng)的穩(wěn)定性凉逛,因此,需要使用線程池來管理線程群井。

2. 線程池的工作原理

當一個并發(fā)任務提交給線程池状飞,線程池分配線程去執(zhí)行任務的過程如下圖所示:

線程池執(zhí)行流程圖.jpg

從圖可以看出,線程池執(zhí)行所提交的任務過程主要有這樣幾個階段:

先判斷線程池中核心線程池所有的線程是否都在執(zhí)行任務书斜。如果不是诬辈,則新創(chuàng)建一個線程執(zhí)行剛提交的任務,否則荐吉,核心線程池中所有的線程都在執(zhí)行任務焙糟,則進入第 2 步;

判斷當前阻塞隊列是否已滿样屠,如果未滿穿撮,則將提交的任務放置在阻塞隊列中;否則痪欲,則進入第 3 步悦穿;

判斷線程池中所有的線程是否都在執(zhí)行任務,如果沒有业踢,則創(chuàng)建一個新的線程來執(zhí)行任務咧党,否則,則交給飽和策略進行處理

3.線程池的實現(xiàn)

不懂的朋友可以看看這個陨亡,線程池現(xiàn)實的視頻講解,點擊:150行代碼深员,手寫線程池

4. 線程池的創(chuàng)建

創(chuàng)建線程池主要是ThreadPoolExecutor類來完成负蠕,ThreadPoolExecutor 的有許多重載的構(gòu)造方法,通過參數(shù)最多的構(gòu)造方法來理解創(chuàng)建線程池有哪些需要配置的參數(shù)倦畅。ThreadPoolExecutor 的構(gòu)造方法為:

ThreadPoolExecutor(int corePoolSize,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int maximumPoolSize,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long keepAliveTime,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue<Runnable> workQueue,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ThreadFactory threadFactory,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? RejectedExecutionHandler handler)

下面對參數(shù)進行說明:

corePoolSize:表示核心線程池的大小遮糖。當提交一個任務時,如果當前核心線程池的線程個數(shù)沒有達到 corePoolSize叠赐,則會創(chuàng)建新的線程來執(zhí)行所提交的任務欲账,即使當前核心線程池有空閑的線程。如果當前核心線程池的線程個數(shù)已經(jīng)達到了 corePoolSize芭概,則不再重新創(chuàng)建線程赛不。如果調(diào)用了prestartCoreThread()或者 prestartAllCoreThreads(),線程池創(chuàng)建的時候所有的核心線程都會被創(chuàng)建并且啟動罢洲。

maximumPoolSize:表示線程池能創(chuàng)建線程的最大個數(shù)踢故。如果當阻塞隊列已滿時,并且當前線程池線程個數(shù)沒有超過 maximumPoolSize 的話,就會創(chuàng)建新的線程來執(zhí)行任務殿较。

keepAliveTime:空閑線程存活時間耸峭。如果當前線程池的線程個數(shù)已經(jīng)超過了 corePoolSize,并且線程空閑時間超過了 keepAliveTime 的話淋纲,就會將這些空閑線程銷毀劳闹,這樣可以盡可能降低系統(tǒng)資源消耗。

unit:時間單位洽瞬。為 keepAliveTime 指定時間單位本涕。

workQueue:阻塞隊列。用于保存任務的阻塞隊列片任,關于阻塞隊列可以看這篇文章偏友。可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue对供。

threadFactory:創(chuàng)建線程的工程類位他。可以通過指定線程工廠為每個創(chuàng)建出來的線程設置更有意義的名字产场,如果出現(xiàn)并發(fā)問題鹅髓,也方便查找問題原因。

handler:飽和策略京景。當線程池的阻塞隊列已滿和指定的線程都已經(jīng)開啟窿冯,說明當前線程池已經(jīng)處于飽和狀態(tài)了,那么就需要采用一種策略來處理這種情況确徙。采用的策略有這幾種:

AbortPolicy: 直接拒絕所提交的任務醒串,并拋出RejectedExecutionException異常;

CallerRunsPolicy:只用調(diào)用者所在的線程來執(zhí)行任務鄙皇;

DiscardPolicy:不處理直接丟棄掉任務芜赌;

DiscardOldestPolicy:丟棄掉阻塞隊列中存放時間最久的任務,執(zhí)行當前任務

分享更多關于C/C++ Linux后端開發(fā)網(wǎng)絡底層原理知識學習提升? 學習資料伴逸,完善技術棧缠沈,內(nèi)容知識點包括Linux,Nginx错蝴,ZeroMQ洲愤,MySQL,Redis顷锰,線程池柬赐,MongoDB,ZK官紫,流媒體躺率,音視頻玛界,Linux內(nèi)核,CDN悼吱,P2P慎框,epoll,Docker后添,TCP/IP笨枯,協(xié)程,DPDK等等遇西。

線程池執(zhí)行邏輯

通過 ThreadPoolExecutor 創(chuàng)建線程池后馅精,提交任務后執(zhí)行過程是怎樣的,下面來通過源碼來看一看粱檀。execute 方法源碼如下:

public void execute(Runnable command) {

? ? if (command == null)

? ? ? ? throw new NullPointerException();

? ? /*

? ? * Proceed in 3 steps:

? ? *

? ? * 1. If fewer than corePoolSize threads are running, try to

? ? * start a new thread with the given command as its first

? ? * task.? The call to addWorker atomically checks runState and

? ? * workerCount, and so prevents false alarms that would add

? ? * threads when it shouldn't, by returning false.

? ? *

? ? * 2. If a task can be successfully queued, then we still need

? ? * to double-check whether we should have added a thread

? ? * (because existing ones died since last checking) or that

? ? * the pool shut down since entry into this method. So we

? ? * recheck state and if necessary roll back the enqueuing if

? ? * stopped, or start a new thread if there are none.

? ? *

? ? * 3. If we cannot queue task, then we try to add a new

? ? * thread.? If it fails, we know we are shut down or saturated

? ? * and so reject the task.

? ? */

? ? int c = ctl.get();

//如果線程池的線程個數(shù)少于corePoolSize則創(chuàng)建新線程執(zhí)行當前任務

? ? if (workerCountOf(c) < corePoolSize) {

? ? ? ? if (addWorker(command, true))

? ? ? ? ? ? return;

? ? ? ? c = ctl.get();

? ? }

//如果線程個數(shù)大于corePoolSize或者創(chuàng)建線程失敗洲敢,則將任務存放在阻塞隊列workQueue中

? ? if (isRunning(c) && workQueue.offer(command)) {

? ? ? ? int recheck = ctl.get();

? ? ? ? if (! isRunning(recheck) && remove(command))

? ? ? ? ? ? reject(command);

? ? ? ? else if (workerCountOf(recheck) == 0)

? ? ? ? ? ? addWorker(null, false);

? ? }

//如果當前任務無法放進阻塞隊列中,則創(chuàng)建新的線程來執(zhí)行任務

? ? else if (!addWorker(command, false))

? ? ? ? reject(command);

}

ThreadPoolExecutor 的 execute 方法執(zhí)行邏輯請見注釋茄蚯。下圖為 ThreadPoolExecutor 的 execute 方法的執(zhí)行示意圖:

execute執(zhí)行過程示意圖.jpg

execute 方法執(zhí)行邏輯有這樣幾種情況:

如果當前運行的線程少于 corePoolSize压彭,則會創(chuàng)建新的線程來執(zhí)行新的任務;

如果運行的線程個數(shù)等于或者大于 corePoolSize渗常,則會將提交的任務存放到阻塞隊列 workQueue 中壮不;

如果當前 workQueue 隊列已滿的話,則會創(chuàng)建新的線程來執(zhí)行任務皱碘;

如果線程個數(shù)已經(jīng)超過了 maximumPoolSize询一,則會使用飽和策略 RejectedExecutionHandler 來進行處理。

需要注意的是癌椿,線程池的設計思想就是使用了核心線程池 corePoolSize健蕊,阻塞隊列 workQueue 和線程池 maximumPoolSize,這樣的緩存策略來處理任務踢俄,實際上這樣的設計思想在需要框架中都會使用绊诲。

5. 線程池的關閉

關閉線程池,可以通過shutdown和shutdownNow這兩個方法褪贵。它們的原理都是遍歷線程池中所有的線程,然后依次中斷線程抗俄。shutdown和shutdownNow還是有不一樣的地方:

shutdownNow首先將線程池的狀態(tài)設置為STOP,然后嘗試停止所有的正在執(zhí)行和未執(zhí)行任務的線程脆丁,并返回等待執(zhí)行任務的列表;

shutdown只是將線程池的狀態(tài)設置為SHUTDOWN狀態(tài)动雹,然后中斷所有沒有正在執(zhí)行任務的線程

可以看出 shutdown 方法會將正在執(zhí)行的任務繼續(xù)執(zhí)行完槽卫,而 shutdownNow 會直接中斷正在執(zhí)行的任務。調(diào)用了這兩個方法的任意一個胰蝠,isShutdown方法都會返回 true歼培,當所有的線程都關閉成功震蒋,才表示線程池成功關閉,這時調(diào)用isTerminated方法才會返回 true躲庄。

5. 如何合理配置線程池參數(shù)查剖?

要想合理的配置線程池,就必須首先分析任務特性噪窘,可以從以下幾個角度來進行分析:

任務的性質(zhì):CPU 密集型任務笋庄,IO 密集型任務和混合型任務。

任務的優(yōu)先級:高倔监,中和低直砂。

任務的執(zhí)行時間:長,中和短浩习。

任務的依賴性:是否依賴其他系統(tǒng)資源静暂,如數(shù)據(jù)庫連接。

任務性質(zhì)不同的任務可以用不同規(guī)模的線程池分開處理谱秽。CPU 密集型任務配置盡可能少的線程數(shù)量洽蛀,如配置Ncpu+1個線程的線程池。IO 密集型任務則由于需要等待 IO 操作弯院,線程并不是一直在執(zhí)行任務辱士,則配置盡可能多的線程,如2xNcpu听绳∷痰猓混合型的任務,如果可以拆分椅挣,則將其拆分成一個 CPU 密集型任務和一個 IO 密集型任務头岔,只要這兩個任務執(zhí)行的時間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率鼠证,如果這兩個任務執(zhí)行時間相差太大峡竣,則沒必要進行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的 CPU 個數(shù)量九。

優(yōu)先級不同的任務可以使用優(yōu)先級隊列 PriorityBlockingQueue 來處理适掰。它可以讓優(yōu)先級高的任務先得到執(zhí)行,需要注意的是如果一直有優(yōu)先級高的任務提交到隊列里荠列,那么優(yōu)先級低的任務可能永遠不能執(zhí)行类浪。

執(zhí)行時間不同的任務可以交給不同規(guī)模的線程池來處理,或者也可以使用優(yōu)先級隊列肌似,讓執(zhí)行時間短的任務先執(zhí)行费就。

依賴數(shù)據(jù)庫連接池的任務,因為線程提交 SQL 后需要等待數(shù)據(jù)庫返回結(jié)果川队,如果等待的時間越長 CPU 空閑時間就越長力细,那么線程數(shù)應該設置越大睬澡,這樣才能更好的利用 CPU。

并且眠蚂,阻塞隊列最好是使用有界隊列煞聪,如果采用無界隊列的話,一旦任務積壓在阻塞隊列中的話就會占用過多的內(nèi)存資源河狐,甚至會使得系統(tǒng)崩潰米绕。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市馋艺,隨后出現(xiàn)的幾起案子栅干,更是在濱河造成了極大的恐慌,老刑警劉巖捐祠,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件布持,死亡現(xiàn)場離奇詭異四瘫,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門仑乌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衣吠,“玉大人贸弥,你說我怎么就攤上這事迈螟。” “怎么了猬膨?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵角撞,是天一觀的道長。 經(jīng)常有香客問我勃痴,道長谒所,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任沛申,我火速辦了婚禮劣领,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铁材。我一直安慰自己尖淘,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布著觉。 她就那樣靜靜地躺著村生,像睡著了一般。 火紅的嫁衣襯著肌膚如雪固惯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天缴守,我揣著相機與錄音葬毫,去河邊找鬼镇辉。 笑死,一個胖子當著我的面吹牛贴捡,可吹牛的內(nèi)容都是我干的忽肛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼烂斋,長吁一口氣:“原來是場噩夢啊……” “哼屹逛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汛骂,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤罕模,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帘瞭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淑掌,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年蝶念,在試婚紗的時候發(fā)現(xiàn)自己被綠了抛腕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡媒殉,死狀恐怖担敌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情廷蓉,我是刑警寧澤全封,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站苦酱,受9級特大地震影響售貌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疫萤,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一颂跨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扯饶,春花似錦恒削、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至每币,卻和暖如春携丁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工梦鉴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留李茫,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓肥橙,卻偏偏與公主長得像魄宏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子存筏,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容