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, 如此反覆。