一文搞懂Go 的并發(fā)調(diào)度模型

線程模型

在細(xì)說 Go 的調(diào)度模型之前,先來說說一般意義的線程模型骨望。線程模型一般分三種硬爆,由用戶級(jí)線程和 OS 線程的不同對(duì)應(yīng)關(guān)系決定的。

N:1擎鸠,即全部用戶線程都映射到一個(gè)OS線程上缀磕,上下文切換成本最低,但無法利用多核資源劣光;

**1:1 **, 一個(gè)用戶線程對(duì)應(yīng)到一個(gè) OS線程上袜蚕, 能利用到多核資源,但是上下文切換成本較高绢涡,這也是 Java Hotspot VM 的默認(rèn)實(shí)現(xiàn)牲剃;

M:N,權(quán)衡上面兩者方案雄可,既能利用多核資源也能盡可能減少上下文切換成本颠黎,但是調(diào)度算法的實(shí)現(xiàn)成本偏高。

為什么 Go Scheduler 需要實(shí)現(xiàn) M:N 的方案滞项?

線程創(chuàng)建開銷大狭归。對(duì)于 OS 線程而言,其很多特性均是操作系統(tǒng)給予的文判,但對(duì)于 Go 程序而言过椎,其中很多特性可能非必要的。這樣一來戏仓,如果是 1:1 的方案疚宇,那么每次** go func(){...} **都需要?jiǎng)?chuàng)建一個(gè) OS 線程,而在創(chuàng)建線程過程中赏殃,OS 線程里某些 Go 用不上的特性會(huì)轉(zhuǎn)化為不必要的性能開銷敷待,不經(jīng)濟(jì)。

減少 Go 垃圾回收的復(fù)雜度仁热。依據(jù)1:1方案榜揖,Go 產(chǎn)生所用用戶級(jí)線程均交由 OS 直接調(diào)度。 Go 的垃圾回收器要求在運(yùn)行時(shí)需要停止所有線程,才能使得內(nèi)存達(dá)到穩(wěn)定一致的狀態(tài)举哟,而 OS 不可能清楚這些思劳,垃圾回收器也不能控制 OS 去阻塞線程。

Go Scheduler 的 M:N 方案出現(xiàn)妨猩,就是為了解決上面的問題潜叛。

Go Scheduler

image

整個(gè)并發(fā)模型的討論都離不開 Go Scheduler 的設(shè)計(jì)實(shí)現(xiàn)。

首先壶硅,需要了解的是威兜,Go Scheduler 由哪些元素構(gòu)成呢?

M: Machine庐椒,就是 OS 線程本身椒舵,數(shù)量可配置;

P: Processor扼睬, 調(diào)度器的核心處理器逮栅,通常表示執(zhí)行上下文悴势,用于匹配 M 和 G 窗宇。P 的數(shù)量不能超過 **GOMAXPROCS **配置數(shù)量,這個(gè)參數(shù)的默認(rèn)值為CPU核心數(shù)特纤;通常一個(gè) P 可以與多個(gè) M 對(duì)應(yīng)军俊,但同一時(shí)刻,這個(gè) P 只能和其中一個(gè) M 發(fā)生綁定關(guān)系捧存;M 被創(chuàng)建之后需要自行在 P 的 free list 中找到 P 進(jìn)行綁定粪躬,沒有綁定 P 的 M,會(huì)進(jìn)入阻塞態(tài)昔穴。

注:GOMAXPROCS 參數(shù)很重要镰官,其決定了 P 的最大數(shù)量,也決定了自旋 M 的最大數(shù)量吗货。何為自旋泳唠,后面會(huì)提到。

G: Goroutine宙搬,Go 的用戶級(jí)線程笨腥,常說的協(xié)程,真正攜帶代碼執(zhí)行邏輯的部分勇垛,由 **go func(){...} **直接生成脖母;

**G0: **其本身也是 G ,也需要跟具體的 M 結(jié)合才能被執(zhí)行闲孤,只不過他比較特殊谆级,其本身就是一個(gè) schedule 函數(shù),這個(gè)函數(shù)包含如下邏輯:

/src/runtime/proc.go:

func schedule() {

這里涉及到另外幾個(gè)概念,本地隊(duì)列哨苛、全局隊(duì)列以及 “竊取”鸽凶。

本地隊(duì)列(local queue): 本地是相對(duì) P 而言的本地,每個(gè) P 維護(hù)一個(gè)本地隊(duì)列建峭;與 P 綁定的 M 中如若生成新的 G玻侥,一般情況下會(huì)放到 P 的本地隊(duì)列;當(dāng)本地隊(duì)列滿了的時(shí)候亿蒸,才會(huì)截取本地隊(duì)列中 “一半” 的元素放入全局隊(duì)列中凑兰;

全局隊(duì)列(global queue):承載本地隊(duì)列“溢出”的 G。為了保證調(diào)度公平性边锁,schedule 過程中有 1/61 的幾率優(yōu)先檢查全局隊(duì)列姑食,否則本地隊(duì)列一直滿載的情況下,全局隊(duì)列中的 G 將永遠(yuǎn)無法被調(diào)度到茅坛;

竊纫舭搿(stealing): 這似乎和 Java Fork-Join 中的 work-stealing 模型很相似,其目的也是一樣贡蓖,就是為了使得空閑(idle)的 M 有活干曹鸠,不空等,提高計(jì)算資源的利用率。竊取也是有章法的,規(guī)則是隨機(jī)從其他 P 的本地隊(duì)列里竊取 “一半” 的 G恕稠。

image

綜上,整個(gè)調(diào)度流程就是:

  • 1/61 的幾率在全局隊(duì)列中找 G邻眷,60/61 的幾率在本地隊(duì)列找 G;

  • 如果全局隊(duì)列找不到 G剔交,從 P 的本地隊(duì)列找 G肆饶;

  • 如果找不到,從其他 P 的本地隊(duì)列中竊取 G岖常;

  • 如果找不到驯镊,則從全局隊(duì)列中拿取一部分 G 到本地隊(duì)列。這里拿取的 “一部分” 滿足一個(gè)公式:

    n = min(len(GQ)/GOMAXPROCS + 1, len(GQ/2))

    注:這里 GQ 表示全局隊(duì)列腥椒。

  • 如果找不到阿宅,從網(wǎng)絡(luò)中 poll G。

只要找到了 G笼蛛, 就會(huì)立馬丟給 M 執(zhí)行洒放。當(dāng)然上述任何執(zhí)行邏輯如果沒有 running 的 M 參與,都是無法真正被執(zhí)行的滨砍,這包括調(diào)度邏輯本身往湿。

一言蔽之妖异,調(diào)度的本質(zhì)就是 P 將 G 合理的分配給某個(gè) M 的過程。

線程自旋(Spinning Threads)

線程自旋是相對(duì)于線程阻塞而言的领追,表象就是循環(huán)執(zhí)行一個(gè)指定邏輯(就是上面提到的調(diào)度邏輯他膳,目的是不停地尋找 G)。這樣做的問題顯而易見绒窑,如果 G 遲遲不來棕孙,CPU 會(huì)白白浪費(fèi)在這無意義的計(jì)算上。但好處也很明顯些膨,降低了 M 的上下文切換成本蟀俊,提高了性能。

具體來說订雾,假設(shè)Scheduler 中全局和本地隊(duì)列均為空肢预,M 此時(shí)沒有任何任務(wù)可以處理,那么你會(huì)選擇讓 M 進(jìn)入阻塞狀態(tài)還是選擇讓 CPU 空轉(zhuǎn)等待 G 的駕臨洼哎?

Go 的設(shè)計(jì)者傾向于高性能的并發(fā)表現(xiàn)烫映,選擇了后者。當(dāng)然前面也提到過噩峦,為了避免過多浪費(fèi) CPU 資源锭沟,自旋的線程數(shù)不會(huì)超過 **GOMAXPROCS **,這是因?yàn)橐粋€(gè) P 在同一個(gè)時(shí)刻只能綁定一個(gè) M壕探,P的數(shù)量不會(huì)超過 GOMAXPROCS冈钦,自然被綁定的 M 的數(shù)量也不會(huì)超過郊丛。對(duì)于未被綁定的“游離態(tài)”的 M李请,會(huì)進(jìn)入休眠阻塞態(tài)。

M 如果因?yàn)?G 發(fā)起了系統(tǒng)調(diào)用進(jìn)入了阻塞態(tài)會(huì)怎樣厉熟?

image

如圖导盅,如果 G8 發(fā)起了阻塞系統(tǒng)調(diào)用(例如阻塞 IO 操作),使得對(duì)應(yīng)的 M2 進(jìn)入了阻塞態(tài)揍瑟。此時(shí)如果沒有任何的處理白翻,Go Scheduler 就會(huì)在這段阻塞的時(shí)間內(nèi),白白缺失了一個(gè) OS 線程單元绢片。

Go 設(shè)計(jì)者的解決方案是滤馍,一旦 G8 發(fā)起 Syscall 使得 M2 進(jìn)入阻塞態(tài),此時(shí)的 P2 會(huì)立即與 M2 解綁底循,保留 M2 與 G8 的關(guān)系巢株,繼而與新的 OS 線程 M5 綁定,繼續(xù)下一輪的調(diào)度熙涤。那么雖然 M2 進(jìn)入了阻塞態(tài)阁苞,但宏觀來看困檩,并沒有缺失任何處理單元,P2 依然正常工作那槽。

那 G8 的阻塞操作返回后怎么辦悼沿?

G8 失去了 P2,意味著失去了執(zhí)行機(jī)會(huì)骚灸,M2 被喚醒以后第一件事就是要竊取一個(gè)上下文(Processor)糟趾,還給 G8 執(zhí)行機(jī)會(huì)。然而現(xiàn)實(shí)是 M2 不一定能夠找到 P 綁定甚牲,不過找不到也沒關(guān)系拉讯,M2 會(huì)將 G8 丟到全局隊(duì)列中,等待調(diào)度鳖藕。

這樣一來 G8 會(huì)被其他的 M 調(diào)度到魔慷,重新獲得執(zhí)行機(jī)會(huì),繼續(xù)執(zhí)行阻塞返回之后的邏輯著恩。

與 Java F-J Task 相比院尔,Goroutine 是否有本質(zhì)區(qū)別?

直覺告訴我喉誊,這兩者并沒有本質(zhì)區(qū)別邀摆。

兩個(gè)方案均為 M:N,兩者定位都是“輕量級(jí)”的用戶線程伍茄,而并非與 OS 線程一一對(duì)應(yīng)栋盹。引入的本地隊(duì)列使得在大多數(shù)場(chǎng)景下,線程爭(zhēng)搶會(huì)更少敷矫。而從調(diào)度算法細(xì)分析來看例获,Go Scheduler 會(huì)比 F-J 更加復(fù)雜,對(duì)線程的控制更加精細(xì)曹仗,其適用的場(chǎng)景也更加全面榨汤。

總之,作為一個(gè) Java 開發(fā)者怎茫,如果你是用 Hotspot VM收壕,是默認(rèn)不支持原生協(xié)程的,但不代表不能實(shí)現(xiàn)所謂的協(xié)程轨蛤,F(xiàn)-J 可以理解為一個(gè)簡(jiǎn)易版實(shí)現(xiàn)蜜宪。

然而 Go 并發(fā)調(diào)度的優(yōu)勢(shì)在哪呢?

原生支持祥山;調(diào)度算法更加科學(xué)圃验,更具備普適性;另外枪蘑,簡(jiǎn)潔的語法掩蓋了底層細(xì)節(jié)损谦,這讓開發(fā)者輕松實(shí)現(xiàn)高性能并發(fā)編程岖免,成為可能。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末照捡,一起剝皮案震驚了整個(gè)濱河市颅湘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌栗精,老刑警劉巖闯参,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異悲立,居然都是意外死亡鹿寨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門薪夕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脚草,“玉大人,你說我怎么就攤上這事原献×罂” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵姑隅,是天一觀的道長(zhǎng)写隶。 經(jīng)常有香客問我,道長(zhǎng)讲仰,這世上最難降的妖魔是什么慕趴? 我笑而不...
    開封第一講書人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮鄙陡,結(jié)果婚禮上冕房,老公的妹妹穿的比我還像新娘。我一直安慰自己柔吼,他們只是感情好毒费,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開白布丙唧。 她就那樣靜靜地躺著愈魏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪想际。 梳的紋絲不亂的頭發(fā)上培漏,一...
    開封第一講書人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音胡本,去河邊找鬼牌柄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛侧甫,可吹牛的內(nèi)容都是我干的珊佣。 我是一名探鬼主播蹋宦,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咒锻!你這毒婦竟也來了冷冗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤惑艇,失蹤者是張志新(化名)和其女友劉穎蒿辙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滨巴,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡思灌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恭取。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泰偿。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜈垮,靈堂內(nèi)的尸體忽然破棺而出甜奄,到底是詐尸還是另有隱情,我是刑警寧澤窃款,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布课兄,位于F島的核電站,受9級(jí)特大地震影響晨继,放射性物質(zhì)發(fā)生泄漏烟阐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一紊扬、第九天 我趴在偏房一處隱蔽的房頂上張望蜒茄。 院中可真熱鬧,春花似錦餐屎、人聲如沸檀葛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屿聋。三九已至,卻和暖如春藏鹊,著一層夾襖步出監(jiān)牢的瞬間润讥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工盘寡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留楚殿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓竿痰,卻偏偏與公主長(zhǎng)得像脆粥,于是被迫代替她去往敵國(guó)和親砌溺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354

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