深入學習 Golang GMP 調(diào)度器

本文是 「Golang 并發(fā)編程」系列的第一篇,筆者水平有限辛润,歡迎各位大佬指點~

## 1. 前言

Go 語言最大的魅力就是只需要 `go` 關(guān)鍵字即可快速創(chuàng)建一個 goroutine 膨处,無需關(guān)注操作系統(tǒng)的調(diào)度細節(jié),即可利用上多核輕松開發(fā)出高并發(fā)的服務(wù)器應(yīng)用。理解了 Golang 調(diào)度器的機制真椿,能讓我們寫出高性能的并發(fā)程序鹃答,也可以提升我們的問題定位、性能優(yōu)化的能力突硝。

## 2. 進程测摔、線程、協(xié)程

任何**并發(fā)**程序解恰,無論在應(yīng)用層是何形式锋八,最終都要被操作系統(tǒng)所管理。由此涉及到以下幾個概念:

- 進程 (Process):操作系統(tǒng)分配資源的基本單位

- 線程 (Thread):CPU 調(diào)度的基本單位护盈,一個核上同時只能運行一個線程挟纱,單線程的棧內(nèi)存大小約 **1MB**

- 協(xié)程 (Coroutine):輕量級、用戶態(tài)線程黄琼。在 Go 語言中樊销,有 `goroutine`的概念。當一個 `goroutine` 被創(chuàng)建出來時,分配的棧大小是 **2KB**衬衬,可見其「輕量」

## 3. 早期的調(diào)度模型:GM 模型

系統(tǒng)的設(shè)計往往都不是一蹴而就的奄喂,伴隨著演進和優(yōu)化,Golang scheduler 也不例外剂府。`1.1` 版本前的 go,使用的是相對簡單的 `GM` 模型來處理 goroutine 的調(diào)度剃盾,`GM` 實際是兩個結(jié)構(gòu)體腺占,即:

- G(Goroutine): 協(xié)程結(jié)構(gòu)體,使用 `go func()` 時痒谴,就會創(chuàng)建一個 `G`

- M(Machine): 處理線程操作的結(jié)構(gòu)體衰伯,與操作系統(tǒng)直接交互

GM 模型包含一個**全局協(xié)程隊列**,即所有新建的 `G` 對象都會排隊等待 `M` 的處理积蔚。如圖:

![圖片來自 https://learnku.com/articles/41728](https://gitee.com/cxy_mrzero/pic/raw/master/2022-2-12/1644680950677-gm%E8%B0%83%E5%BA%A6.png )

這樣的設(shè)計在高并發(fā)下會帶來以下問題:

>? What’s wrong with current implementation:

>? 1. Single global mutex (Sched.Lock) and centralized state. The mutex protects all goroutine-related operations (creation, completion, rescheduling, etc).

>? 2. Goroutine (G) hand-off (G.nextg). Worker threads (M’s) frequently hand-off runnable goroutines between each other, this may lead to increased latencies and additional overheads. Every M must be able to execute any runnable G, in particular the M that just created the G.

>? 3. Per-M memory cache (M.mcache). Memory cache and other caches (stack alloc) are associated with all M’s, while they need to be associated only with M’s running Go code (an M blocked inside of syscall does not need mcache). A ratio between M’s running Go code and all M’s can be as high as 1:100. This leads to excessive resource consumption (each MCache can suck up up to 2M) and poor data locality.

> 4. Aggressive thread blocking/unblocking. In presence of syscalls worker threads are frequently blocked and unblocked. This adds a lot of overhead.

(原文見 [Scalable Go Scheduler Design Doc](https://xie.infoq.cn/article/a6948402be688dba530094e9b))

翻譯總結(jié)一下意鲸,即存在以下優(yōu)化點:

1. **全局鎖、中心化狀態(tài)帶來的鎖競爭導致的性能下降**

2. M 會頻繁交接 G尽爆,導致額外開銷怎顾、性能下降。每個 M 都得能執(zhí)行任意的 runnable 狀態(tài)的 G

3. 對每個 M 的不合理的內(nèi)存緩存分配漱贱,進行系統(tǒng)調(diào)用被阻塞住的 M 其實不需要分配內(nèi)存緩存

4. 強線程阻塞/解阻塞槐雾。頻繁的系統(tǒng)調(diào)用導致的線程阻塞/解阻塞帶來了大量的系統(tǒng)開銷。

由此演進出經(jīng)典的 `GMP` 調(diào)度模型

## 4. 高效的 GMP 調(diào)度模型

為了解決 `G` 和 `M` 調(diào)度低效的問題幅狮,中間層 `P(Processor)` 被引入了募强。

- `P(Processor)` 即管理 `goroutine` 資源的處理器

由此得以將原先的系統(tǒng)線程資源管理 `M` 與 `goroutine` 對象 `G` 解耦株灸。

除此之外,GMP 還引入了幾個新概念:

- `LRQ (Local Runnable Queue)` 本地可運行隊列钻注,這個「本地」蚂且,是針對 P 而言的,是指掛載在一個 P 上的可運行 G 隊列

- `GRQ (Global Runnable Queue)` 全局可運行隊列

如下圖所示:

![LRQ 與 GRQ](https://static001.geekbang.org/infoq/a5/a54650ec9565b8c4fa771683d23db31c.png)

從 runtime 源碼可知幅恋,整體的調(diào)度流程如下:

```go

runtime.schedule() {

? ? // only 1/61 of the time, check the global runnable queue for a G.

? ? // if not found, check the local queue.

? ? // if not found,

? ? //? ? try to steal from other Ps.

? ? //? ? if not, check the global runnable queue.

? ? //? ? if not found, poll network.

}

```

翻譯一下杏死,即

- 只有 1/61 的情況下,會檢查 GRQ 來獲取一個 G 來運行

- 如果沒找到捆交,則檢查 LRQ

- 如果 LRQ 也為空淑翼,則嘗試從其它 P 上偷 G

- 如果偷不到,就再檢查 GRQ

- 如果還是沒事干品追,就會從 `Network Poller` 上拿一個玄括。這里的 `Network Poller` 是 go 管理網(wǎng)絡(luò)調(diào)用的模塊,如下圖肉瓦,當出現(xiàn)網(wǎng)絡(luò) IO 時遭京,就會將當前的 G 移交到 `Network Poller` 處理。P.S. `Network Poller` 的實現(xiàn)泞莉,又可以扯一篇文章出來了哪雕,以后有空專門開一篇分析。

![G1 正在 M 上運行且需要進行網(wǎng)絡(luò)調(diào)用了](https://gitee.com/cxy_mrzero/pic/raw/master/2022-2-14/1644814606401-GPM-with-network-poller.png)

![于是 G1 被挪到 Net Poller 進行異步網(wǎng)絡(luò)調(diào)用鲫趁,現(xiàn)在 M 就可以執(zhí)行 G2 了](https://gitee.com/cxy_mrzero/pic/raw/master/2022-2-15/1644937037940-gmp-g-on-netpoller.png)

### Work-stealing 機制

`GMP`高性能的關(guān)鍵斯嚎,在于引入了 `work-stealing` 機制,如下圖所示:

![work-stealing 機制](https://gitee.com/cxy_mrzero/pic/raw/master/2022-2-14/1644814707964-scheduler-stealing.png)

> 當一個新的 G 被創(chuàng)建時挨厚,它會被追加到它當前 P 的 LRQ 隊尾堡僻。當一個 G 被執(zhí)行完時,它當前的 P 會先嘗試從自己的 LRQ 中 pop 一個 G 出來執(zhí)行疫剃,如果此時隊列為空钉疫,則會**隨機選取一個** P **并偷走它一半的** G ,對應(yīng)圖中巢价,也就是 3 個 G

這就好比公司里的卷王牲阁,自己的需求做完了,還要悄悄把摸魚大王的需求清單里一半的需求都掛到自己名下蹄溉,囧

### 最后一個疑問:GRQ 什么時候被寫入呢?

這又涉及到 G 創(chuàng)建時的分配流程了您炉,我們知道柒爵,goroutine 都是由老的 goroutine 分配出來的,main 函數(shù)也不例外赚爵,因此每個 G 被分配的時候已經(jīng)有一個老 G 和對應(yīng)的老 P 了棉胀,在掛載 G 到 P 上時法瑟,也會優(yōu)先掛在到老 P 的 LRQ 上去。但是老 P 的 LRQ 其實是有限的唁奢,當掛滿的時候霎挟,這個新 G 就只能掛到 GRQ 上,等待被調(diào)度了麻掸∷重玻可以參考源碼中 P 的定義,其中 `runq` 就是 GRQ 了:

```go

type p struct {

? ...

? ? // Queue of runnable goroutines. Accessed without lock.

? runqhead uint32

? runqtail uint32

? runq? ? [256]guintptr

? ? ...

}

```

## 5. 小結(jié)

本文講解了 go 語言調(diào)度器的發(fā)展及基于 GMP 的 work-stealing 策略脊奋,這個「偷」可以說是非常精妙啦~ 這是 「#Golang 網(wǎng)絡(luò)編程」 系列的第一篇文章熬北,如果對你有幫助,可以點個關(guān)注/在看來激勵我寫文章~

To be continued...

### 參考資料

- 《Head First of Golang Scheduler》https://zhuanlan.zhihu.com/p/42057783?

- 《動圖圖解诚隙!GMP模型里為什么要有P讶隐?背后的原因讓人暖心 | Go主題月》https://juejin.cn/post/6956008643456139301?

- 《Golang 調(diào)度器 GMP 原理與調(diào)度全分析》https://learnku.com/articles/41728

- 《Go's work-stealing scheduler》https://rakyll.org/scheduler/

- 《Scheduling In Go : Part II - Go Scheduler》https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part2.html

- 《Go的隱秘世界:Goroutine調(diào)度機制概覽》https://zhuanlan.zhihu.com/p/244054940

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市久又,隨后出現(xiàn)的幾起案子巫延,更是在濱河造成了極大的恐慌,老刑警劉巖地消,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炉峰,死亡現(xiàn)場離奇詭異,居然都是意外死亡犯建,警方通過查閱死者的電腦和手機讲冠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來适瓦,“玉大人竿开,你說我怎么就攤上這事〔N酰” “怎么了否彩?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗦随。 經(jīng)常有香客問我列荔,道長,這世上最難降的妖魔是什么枚尼? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任贴浙,我火速辦了婚禮,結(jié)果婚禮上署恍,老公的妹妹穿的比我還像新娘崎溃。我一直安慰自己,他們只是感情好盯质,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布袁串。 她就那樣靜靜地躺著概而,像睡著了一般。 火紅的嫁衣襯著肌膚如雪囱修。 梳的紋絲不亂的頭發(fā)上赎瑰,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機與錄音破镰,去河邊找鬼餐曼。 笑死,一個胖子當著我的面吹牛啤咽,可吹牛的內(nèi)容都是我干的晋辆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宇整,長吁一口氣:“原來是場噩夢啊……” “哼瓶佳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳞青,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤霸饲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后臂拓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厚脉,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年胶惰,在試婚紗的時候發(fā)現(xiàn)自己被綠了傻工。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡孵滞,死狀恐怖中捆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坊饶,我是刑警寧澤泄伪,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站匿级,受9級特大地震影響蟋滴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜痘绎,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一津函、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孤页,春花似錦尔苦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悲龟,卻和暖如春屋讶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背须教。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工皿渗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人轻腺。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓乐疆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贬养。 傳聞我的和親對象是個殘疾皇子挤土,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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