GM 到 GMP,Golang 經歷了什麼?

超超和麪試官聊完了進程到協程發展史之後,面試官似乎想在 GMP 模型上對超超 “痛下殺手”,下面來看超超能不能接住面試官的大殺器吧!

GM 模型

面試官:你知道 GMP 之前用的是 GM 模型嗎?

超超:這個我知道,在 12 年的 go1.1 版本之前用的都是 GM 模型,但是由於 GM 模型性能不好,飽受用戶詬病。之後官方對調度器進行了改進,變成了我們現在用的 GMP 模型。

面試官:那你能給我說說什麼是 GM 模型?爲什麼效率不好呢?

考點:GM 模型

超超:GM 模型中的 G 全稱爲 Goroutine 協程,M 全稱爲 Machine 內核級線程,調度過程如下

M(內核線程) 從加鎖的 Goroutine 隊列中獲取 G(協程) 執行,如果 G 在運行過程中創建了新的 G,那麼新的 G 也會被放入全局隊列中。

很顯然這樣做有倆個缺點,一是調度,返回 G 都需要獲取隊列鎖,形成了激烈的競爭。二是 M 轉移 G 沒有把資源最大化利用。比如當 M1 在執行 G1 時,M1 創建了 G2,爲了繼續執行 G1,需要把 G2 交給 M2 執行,因爲 G1 和 G2 是相關的,而寄存器中會保存 G1 的信息,因此 G2 最好放在 M1 上執行,而不是其他的 M。

GMP

面試官:那你能給我說說 GMP 模型是怎麼設計的嗎?

考點:GMP 設計

超超:G 全稱爲 Goroutine 協程,M 全稱爲 Machine 內核級線程,P 全稱爲 Processor 協程運行所需的資源,他在 GM 的基礎上增加了一個 P 層,下面我們來看一下他是如何設計的。

全局隊列:當 P 中的本地隊列中有協程 G 溢出時,會被放到全局隊列中。

P 的本地隊列:P 內置的 G 隊列,存的數量有限,不超過 256 個。這裏有倆種特殊情況。一是當隊列 P1 中的 G1 在運行過程中新建 G2 時,G2 優先存放到 P1 的本地隊列中,如果隊列滿了,則會把 P1 隊列中一半的 G 移動到全局隊列。二是如果 P 的本地隊列爲空,那麼他會先到全局隊列中獲取 G,如果全局隊列中也沒有 G,則會嘗試從其他線程綁定的 P 中偷取一半的 G。

面試官:P 和 M 數量是可以無限擴增的嗎?

考點:GMP 細節

超超:是不能無限擴增的,無限擴增系統也承受不了呀,哈哈

P 的數量:由啓動時環境變量$GOMAXPROCS或者是由runtime的方法GOMAXPROCS()決定。

M 的數量:go 程序啓動時,會設置 M 的最大數量,默認 10000。但是內核很難創建出如此多的線程,因此默認情況下 M 的最大數量取決於內核。也可以調用 runtime/debug 中的 SetMaxThreads 函數,手動設置 M 的最大數量。

面試官:那 P 和 M 都是在程序運行時就被創建好了嗎?

考點:繼續深挖 GMP 細節

超超:P 和 M 創建的時機是不同的

P 何時創建:在確定了 P 的最大數量 n 後,運行時系統會根據這個數量創建 n 個 P。

M 何時創建:內核級線程的初始化是由內核管理的,當沒有足夠的 M 來關聯 P 並運行其中的可運行的 G 時會請求創建新的 M。比如 M 在運行 G1 時被阻塞住了,此時需要新的 M 去綁定 P,如果沒有在休眠的 M 則需要新建 M。

面試官:你能給我說說當 M0 將 G1 執行結束後會怎樣做嗎?

考點:G 在 GMP 模型中流動過程

超超:那我給你舉個例子吧(:這次把整個過程都說完,看你還能問什麼

(圖轉自劉丹冰 Golang 的協程調度器原理及 GMP 設計思想)

  1. 調用 go func() 創建一個 goroutine;

  2. 新創建的 G 優先保存在 P 的本地隊列中,如果 P 的本地隊列已經滿了就會保存在全局的隊列中;

  3. M 需要在 P 的本地隊列彈出一個可執行的 G,如果 P 的本地隊列爲空,則先會去全局隊列中獲取 G,如果全局隊列也爲空則去其他 P 中偷取 G 放到自己的 P 中

  4. G 將相關參數傳輸給 M,爲 M 執行 G 做準備

  5. 當 M 執行某一個 G 時候如果發生了系統調用產生導致 M 會阻塞,如果當前 P 隊列中有一些 G,runtime 會將線程 M 和 P 分離,然後再獲取空閒的線程或創建一個新的內核級的線程來服務於這個 P,阻塞調用完成後 G 被銷燬將值返回;

  6. 銷燬 G,將執行結果返回

  7. 當 M 系統調用結束時候,這個 M 會嘗試獲取一個空閒的 P 執行,如果獲取不到 P,那麼這個線程 M 變成休眠狀態, 加入到空閒線程中。

GM 與 GMP

面試官:看來你對 GMP 整個流程還是比較清楚的,那你再給我說說 GMP 相對於 GM 做了哪些優化吧。

考點:GM 與 GMP 區別

超超:優化點有三個,一是每個 P 有自己的本地隊列,而不是所有的 G 操作都要經過全局的 G 隊列,這樣鎖的競爭會少的多的多。而 GM 模型的性能開銷大頭就是鎖競爭。

二是 P 的本地隊列平衡上,在 GMP 模型中也實現了 Work Stealing 算法,如果 P 的本地隊列爲空,則會從全局隊列或其他 P 的本地隊列中竊取可運行的 G 來運行(通常是偷一半),減少空轉,提高了資源利用率。

三是 hand off 機制當 M0 線程因爲 G1 進行系統調用阻塞時,線程釋放綁定的 P,把 P 轉移給其他空閒的線程 M1 執行,同樣也是提高了資源利用率。

面試官:你有沒有想過隊列和線程的優化可以做在 G 層和 M 層,爲什麼要加一個 P 層呢?

考點:深挖 GMP

超超:這是因爲 M 層是放在內核的,我們無權修改,在前面協程的問題中回答過,內核級也是用戶級線程發展成熟才加入內核中。所以在 M 無法修改的情況下,所有的修改只能放在用戶層。將隊列和 M 綁定,由於 hand off 機制 M 會一直擴增,因此隊列也需要一直擴增,那麼爲了使 Work Stealing 能夠正常進行,隊列管理將會變的複雜。因此設定了 P 層作爲中間層,進行隊列管理,控制 GMP 數量(最大個數爲 P 的數量)。

面試官:你對 GMP 還是蠻瞭解的哈,那回到剛開始的話題,你知道 mac 中的回收站只能單開,訪達窗口可以多開吧?

超超:知道呀,這是單例模式(:爲什麼 mac 這個點過不去了😫

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/m5NU6hR-PsjsQyLCtFaT8w