GMP

GMP

含義

Goroutine的並發編程模型基於GMP模型,簡要解釋一下GMP的含義:

G:表示goroutine,每個goroutine都有自己的棧空間,定時器,初始化的棧空間在2k左右,空間會隨著需求增長。

M:抽象化代表內核工作執行緒,記錄內核執行緒棧資訊,當goroutine調度到執行緒時,使用該goroutine自己的棧資訊。

P:代表調度器,負責調度goroutine到M,維護一個本地goroutine隊列,M從P上獲得goroutine並執行,同時還負責部分記憶體的管理。

模型

從大體看一下GMP模型。

M代表一個工作執行緒,在M上有一個P和G綁定,P是綁定到M上的,G是通過P的調度獲取的,在某一時刻,一個M上只有一個G(g0除外)。在P上擁有一個G隊列,裡面是已經就緒的G,是可以被調度到執行緒棧上執行的協程,稱為運行隊列。

接下來看一下程式中GMP的分布。

​ 每個進程都有一個全局的G隊列,也擁有P的本地執行隊列,同時也有不在運行隊列中的G。如正處於channel的阻塞狀態的G,還有脫離P綁定在M的(系統調用)G,還有執行結束後進入P的gFree列表中的G等等,接下來列舉一下常見的幾種狀態。

狀態匯總

G狀態

G的主要幾種狀態:

本文基於Go1.13,具體程式碼見(<GOROOT>/src/runtime/runtime2.go)

_Gidle:剛剛被分配並且還沒有被初始化,值為0,為創建goroutine後的默認值

_Grunnable: 沒有執行程式碼,沒有棧的所有權,存儲在運行隊列中,可能在某個P的本地隊列或全局隊列中(如上圖)。

_Grunning: 正在執行程式碼的goroutine,擁有棧的所有權(如上圖)。

_Gsyscall:正在執行系統調用,擁有棧的所有權,與P脫離,但是與某個M綁定,會在調用結束後被分配到運行隊列(如上圖)。

_Gwaiting:被阻塞的goroutine,阻塞在某個channel的發送或者接收隊列(如上圖)。

_Gdead: 當前goroutine未被使用,沒有執行程式碼,可能有分配的棧,分布在空閑列表gFree,可能是一個剛剛初始化的goroutine,也可能是執行了goexit退出的goroutine(如上圖)。

_Gcopystac:棧正在被拷貝,沒有執行程式碼,不在運行隊列上,執行權在

_Gscan : GC 正在掃描棧空間,沒有執行程式碼,可以與其他狀態同時存在

P的狀態

_Pidle :處理器沒有運行用戶程式碼或者調度器,被空閑隊列或者改變其狀態的結構持有,運行隊列為空

_Prunning :被執行緒 M 持有,並且正在執行用戶程式碼或者調度器(如上圖)

_Psyscall:沒有執行用戶程式碼,當前執行緒陷入系統調用(如上圖)

_Pgcstop :被執行緒 M 持有,當前處理器由於垃圾回收被停止

_Pdead :當前處理器已經不被使用

M的狀態

自旋執行緒(休眠狀態):處於運行狀態但是沒有可執行G的執行緒,數量最多為GOMAXPROC,若是數量大於GOMAXPROC就會進入休眠。

非自旋執行緒(非休眠狀態):處於運行狀態有可執行goroutine的執行緒。

調度場景

Channel阻塞:當goroutine讀寫channel發生阻塞時候,會調用gopark函數,該G會脫離當前的M與P,調度器會執行schedule函數調度新的G到當前M。可參考上一篇文章channel探秘。

系統調用:當某個G由於系統調用陷入內核態時,該P就會脫離當前的M,此時P會更新自己的狀態為Psyscall,M與G互相綁定,進行系統調用。結束以後若該P狀態還是Psyscall,則直接關聯該M和G,否則使用閑置的處理器處理該G。

系統監控:當某個G在P上運行的時間超過10ms時候,或者P處於Psyscall狀態過長等情況就會調用retake函數,觸發新的調度。

主動讓出:由於是協作式調度,該G會主動讓出當前的P,更新狀態為Grunnable,該P會調度隊列中的G運行。

總結

runtime 準備好 G, M, P, 然後 M 綁定 P, M 從各種隊列中獲取 G, 切換到 G 的執行棧上並執行 G 上的任務函數,調用 goexit 做清理工作並回到 M, 如此反覆。