[go 語(yǔ)言]go goroutine調(diào)度機(jī)制 && goroutine池

[toc]



摘要

使用go語(yǔ)言寫(xiě)程序差不多有半年多了珊燎,也對(duì)go語(yǔ)言有了更深的理解殿怜,今天聊聊go goroutine的調(diào)度原理。

線程

進(jìn)程:進(jìn)程是并發(fā)執(zhí)行程序在執(zhí)行過(guò)程中資源分配和管理的基本單位(資源分配的最小單位)恬叹。進(jìn)程可以理解為一個(gè)應(yīng)用程序的執(zhí)行過(guò)程腹躁,應(yīng)用程序一旦執(zhí)行,就是一個(gè)進(jìn)程募壕。每個(gè)進(jìn)程都有自己獨(dú)立的地址空間调炬,每啟動(dòng)一個(gè)進(jìn)程,系統(tǒng)就會(huì)為它分配地址空間舱馅,建立數(shù)據(jù)表來(lái)維護(hù)代碼段缰泡、堆棧段和數(shù)據(jù)段。

線程:程序執(zhí)行的最小單位代嗤。

并發(fā)編程的目的是為了讓程序運(yùn)行得更快棘钞,但是并不是啟動(dòng)更多的線程就能讓程序最大限度地并發(fā)執(zhí)行。在進(jìn)行并發(fā)編程時(shí)干毅,如果希望通過(guò)多線程執(zhí)行任務(wù)讓程序運(yùn)行得更快宜猜,會(huì)面臨非常多的挑戰(zhàn),比如上下文切換的問(wèn)題硝逢、死鎖的問(wèn)題姨拥,以及受限于硬件和軟件的資源限制問(wèn)題绅喉。

上下文切換

即使是單核CPU也支持多線程執(zhí)行代碼,CPU通過(guò)給每個(gè)線程分配CPU時(shí)間片來(lái)實(shí)現(xiàn)這個(gè)機(jī)制叫乌。時(shí)間片是CPU分配給各個(gè)線程的時(shí)間柴罐,因?yàn)闀r(shí)間片非常短,所以CPU通過(guò)不停地切換線程執(zhí)行憨奸,讓我們感覺(jué)多個(gè)線程時(shí)同時(shí)執(zhí)行的革屠,時(shí)間片一般是幾十毫秒(ms)。

CPU通過(guò)時(shí)間片分配算法來(lái)循環(huán)執(zhí)行任務(wù)排宰,當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后會(huì)切換到下一個(gè)任務(wù)似芝。但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài)板甘,以便下次切換回這個(gè)任務(wù)時(shí)国觉,可以再次加載這個(gè)任務(wù)的狀態(tài),從任務(wù)保存到再加載的過(guò)程就是一次上下文切換虾啦。

這就像我們同時(shí)讀兩本書(shū)麻诀,當(dāng)我們?cè)谧x一本英文的技術(shù)書(shū)籍時(shí),發(fā)現(xiàn)某個(gè)單詞不認(rèn)識(shí)傲醉,于是便打開(kāi)中英文詞典蝇闭,但是在放下英文書(shū)籍之前,大腦必須先記住這本書(shū)讀到了多少頁(yè)的第多少行硬毕,等查完單詞之后呻引,能夠繼續(xù)讀這本書(shū)。這樣的切換是會(huì)影響讀書(shū)效率的吐咳,同樣上下文切換也會(huì)影響多線程的執(zhí)行速度逻悠。

在高并發(fā)應(yīng)用中頻繁創(chuàng)建線程會(huì)造成不必要的開(kāi)銷,所以有了線程池韭脊。

線程池

線程池中預(yù)先保存一定數(shù)量的線程童谒,而新任務(wù)將不再以創(chuàng)建線程的方式去執(zhí)行,而是將任務(wù)發(fā)布到任務(wù)隊(duì)列沪羔,線程池中的線程不斷的從任務(wù)隊(duì)列中取 出任務(wù)并執(zhí)行饥伊,可以有效的減少線程創(chuàng)建和銷毀所帶來(lái)的開(kāi)銷。

下圖展示一個(gè)典型的線程池:

image-20210210113625319

G往往代表一個(gè)函數(shù)蔫饰。線程池中的線程worker線程不斷的從任務(wù)隊(duì)列中取出任務(wù)并執(zhí)行琅豆。而worker線程的調(diào)度則交給操作系統(tǒng)進(jìn)行調(diào)度。

如果worker線程執(zhí)行的G任務(wù)中發(fā)生系統(tǒng)調(diào)用篓吁,則操作系統(tǒng)會(huì)將該線程置為阻塞狀態(tài)茫因,也意味著該線程在怠工,也意味著消費(fèi)任務(wù)隊(duì)列的worker線程變少了杖剪,也就是說(shuō)線程池消費(fèi)任務(wù)隊(duì)列的能力變?nèi)趿恕?/p>

如果任務(wù)隊(duì)列中的大部分任務(wù)都會(huì)進(jìn)行系統(tǒng)調(diào)用冻押,則會(huì)讓這種狀態(tài)惡化驰贷,大部分worker線程進(jìn)入阻塞狀態(tài),從而任務(wù)隊(duì)列中的任務(wù)產(chǎn)生堆積翼雀。

解決這個(gè)問(wèn)題的一個(gè)思路就是重新審視線程池中線程的數(shù)量,增加線程池中線程數(shù)量可以一定程度上提高消費(fèi)能力孩擂, 但隨著線程數(shù)量增多狼渊,由于過(guò)多線程爭(zhēng)搶CPU,消費(fèi)能力會(huì)有上限类垦,甚至出現(xiàn)消費(fèi)能力下降狈邑。

image.png

這個(gè)問(wèn)題如何解呢?

Goroutine調(diào)度器

線程數(shù)過(guò)多蚤认,意味著操作系統(tǒng)會(huì)不斷的切換線程米苹,頻繁的上下文切換就成了性能瓶頸。Go提供一種機(jī)制砰琢,可以在線程中自己實(shí)現(xiàn)調(diào)度蘸嘶,上下文切換更輕量,從而達(dá)到了線程數(shù)少陪汽,而并發(fā)數(shù)并不少的效果训唱。而線程中調(diào)度的就是 Goroutine.
Goroutine 調(diào)度器的工作就是把“ready-to- run”的goroutine分發(fā)到線程中。
Goroutine主要概念如下:

  • image-20210210114242083
  • G(Goroutine): 即Go協(xié)程挚冤,每個(gè)go關(guān)鍵字都會(huì)創(chuàng)建一個(gè)協(xié)程况增。
  • M(Machine): 工作線程,在Go中稱為Machine训挡。
  • P(Processor): 處理器(Go中定義的一個(gè)摡念澳骤,不是指CPU),包含運(yùn)行Go代碼的必要資源澜薄,也有調(diào)度 goroutine的能力为肮。

M必須擁有P才可以執(zhí)行G中的代碼,P含有一個(gè)包含多個(gè)G的隊(duì)列肤京,P可以調(diào)度G交由M執(zhí)行弥锄。其關(guān)系如下圖所示:

image.png

圖中M是交給操作系統(tǒng)調(diào)度的線程,M持有一個(gè)P蟆沫,P將G調(diào)度進(jìn)M中執(zhí)行籽暇。P同時(shí)還維護(hù)著一個(gè)包含G的隊(duì)列(圖中灰色部 分),可以按照一定的策略將不能的G調(diào)度進(jìn)M中執(zhí)行饭庞。
P的個(gè)數(shù)在程序啟動(dòng)時(shí)決定戒悠,默認(rèn)情況下等同于CPU的核數(shù),由于M必須持有一個(gè)P才可以運(yùn)行Go代碼舟山,所以同時(shí)運(yùn)行的 M個(gè)數(shù)绸狐,也即線程數(shù)一般等同于CPU的個(gè)數(shù)卤恳,以達(dá)到盡可能的使用CPU而又不至于產(chǎn)生過(guò)多的線程切換開(kāi)銷。
程序中可以使用 runtime.GOMAXPROCS() 設(shè)置P的個(gè)數(shù)寒矿,在某些IO密集型的場(chǎng)景下可以在一定程度上提高性能突琳。

Goroutine調(diào)度策略

隊(duì)列輪轉(zhuǎn)

上圖中可見(jiàn)每個(gè)P維護(hù)著一個(gè)包含G的隊(duì)列,不考慮G進(jìn)入系統(tǒng)調(diào)用或IO操作的情況下符相,P周期性的將G調(diào)度到M中執(zhí)行拆融, 執(zhí)行一小段時(shí)間,將上下文保存下來(lái)啊终,然后將G放到隊(duì)列尾部镜豹,然后從隊(duì)列中重新取出一個(gè)G進(jìn)行調(diào)度。

除了每個(gè)P維護(hù)的G隊(duì)列以外蓝牲,還有一個(gè)全局的隊(duì)列趟脂,每個(gè)P會(huì)周期性的查看全局隊(duì)列中是否有G待運(yùn)行并將期調(diào)度到M中執(zhí)行,全局隊(duì)列中G的來(lái)源例衍,主要有從系統(tǒng)調(diào)用中恢復(fù)的G昔期。之所以P會(huì)周期性的查看全局隊(duì)列,也是為了防止全局隊(duì)列中的G被餓死佛玄。

隊(duì)列輪轉(zhuǎn)

上面說(shuō)到P的個(gè)數(shù)默認(rèn)等于CPU核數(shù)镇眷,每個(gè)M必須持有一個(gè)P才可以執(zhí)行G,一般情況下M的個(gè)數(shù)會(huì)略大于P的個(gè)數(shù)翎嫡,這多 出來(lái)的M將會(huì)在G產(chǎn)生系統(tǒng)調(diào)用時(shí)發(fā)揮作用欠动。類似線程池,Go也提供一個(gè)M的池子惑申,需要時(shí)從池子中獲取具伍,用完放回池 子,不夠用時(shí)就再創(chuàng)建一個(gè)圈驼。

image.png

如圖所示人芽,當(dāng)G0即將進(jìn)入系統(tǒng)調(diào)用時(shí),M0將釋放P绩脆,進(jìn)而某個(gè)空閑的M1獲取P萤厅,繼續(xù)執(zhí)行P隊(duì)列中剩下的G。而M0由于 陷入系統(tǒng)調(diào)用而進(jìn)被阻塞靴迫,M1接替M0的工作惕味,只要P不空閑,就可以保證充分利用CPU玉锌。

M1的來(lái)源有可能是M的緩存池名挥,也可能是新建的。當(dāng)G0系統(tǒng)調(diào)用結(jié)束后主守,跟據(jù)M0是否能獲取到P禀倔,將會(huì)將G0做不同的處理:

  1. 如果有空閑的P榄融,則獲取一個(gè)P,繼續(xù)執(zhí)行G0救湖。
  2. 如果沒(méi)有空閑的P愧杯,則將G0放入全局隊(duì)列,等待被其他的P調(diào)度鞋既。然后M0將進(jìn)入緩存池睡眠

工作量竊取

多個(gè)P中維護(hù)的G隊(duì)列有可能是不均衡的力九,比如下圖:

image.png

豎線左側(cè)中右邊的P已經(jīng)將G全部執(zhí)行完,然后去查詢?nèi)株?duì)列涛救,全局隊(duì)列中也沒(méi)有G畏邢,而另一個(gè)M中除了正在運(yùn)行的G 外业扒,隊(duì)列中還有3個(gè)G待運(yùn)行检吆。此時(shí),空閑的P會(huì)將其他P中的G偷取一部分過(guò)來(lái)耀里,一般每次偷取一半莲祸。偷取完如右圖所 示排截。

小結(jié)

一般來(lái)講,程序運(yùn)行時(shí)就將GOMAXPROCS大小設(shè)置為CPU核數(shù)摊灭,可讓Go程序充分利用CPU。在某些IO密集型的應(yīng)用 里败徊,這個(gè)值可能并不意味著性能最好帚呼。理論上當(dāng)某個(gè)Goroutine進(jìn)入系統(tǒng)調(diào)用時(shí),會(huì)有一個(gè)新的M被啟用或創(chuàng)建皱蹦,繼續(xù)占滿CPU煤杀。但由于Go調(diào)度器檢測(cè)到M被阻塞是有一定延遲的,也即舊的M被阻塞和新的M得到運(yùn)行之間是有一定間隔的沪哺,所以在IO密集型應(yīng)用中不妨把GOMAXPROCS設(shè)置的大一些沈自,或許會(huì)有好的效果。

Goroutine池

同理辜妓,在寫(xiě) go 并發(fā)程序的時(shí)候如果程序會(huì)啟動(dòng)大量的 goroutine 枯途,勢(shì)必會(huì)消耗大量的系統(tǒng)資源(內(nèi)存,CPU)籍滴,同理如果引入池化技術(shù)酪夷,衍生出goroutine池,復(fù)用 goroutine 孽惰,則會(huì)節(jié)省資源捶索,提升性能。

選擇一個(gè)開(kāi)源的Ants為例灰瞻。

ants運(yùn)行原理

image

性能

image

從該Ants demo 測(cè)試吞吐性能對(duì)比可以看出腥例,使用ants的吞吐性能相較于原生 goroutine 可以保持在 2-6 倍的性能壓制辅甥,而內(nèi)存消耗則可以達(dá)到 10-20 倍的節(jié)省優(yōu)勢(shì)。

想要深入了解Ants燎竖,請(qǐng)移步項(xiàng)目地址:https://github.com/panjf2000/ants/blob/master/README_ZH.md

參考

The Go scheduler

多線程上下文切換


你的鼓勵(lì)也是我創(chuàng)作的動(dòng)力

打賞地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末璃弄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子构回,更是在濱河造成了極大的恐慌夏块,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纤掸,死亡現(xiàn)場(chǎng)離奇詭異脐供,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)借跪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門政己,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人掏愁,你說(shuō)我怎么就攤上這事歇由。” “怎么了果港?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵沦泌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我辛掠,道長(zhǎng)谢谦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任萝衩,我火速辦了婚禮回挽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘欠气。我一直安慰自己厅各,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布预柒。 她就那樣靜靜地躺著队塘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宜鸯。 梳的紋絲不亂的頭發(fā)上憔古,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音淋袖,去河邊找鬼鸿市。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焰情。 我是一名探鬼主播陌凳,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼内舟!你這毒婦竟也來(lái)了合敦?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤验游,失蹤者是張志新(化名)和其女友劉穎充岛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體耕蝉,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崔梗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垒在。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒜魄。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖爪膊,靈堂內(nèi)的尸體忽然破棺而出权悟,到底是詐尸還是另有隱情砸王,我是刑警寧澤推盛,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站谦铃,受9級(jí)特大地震影響耘成,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驹闰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一瘪菌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘹朗,春花似錦师妙、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至褪秀,卻和暖如春蓄诽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媒吗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工仑氛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓锯岖,卻偏偏與公主長(zhǎng)得像介袜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子出吹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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