GoLang 的協(xié)程調(diào)度和 GMP 模型

轉(zhuǎn)自:https://blog.csdn.net/yanglingwell/article/details/103538730

GoLang 是怎么啟動的

關(guān)于 GoLang 的匯編語言,請查閱 參考文獻(xiàn)[1] 和 參考文獻(xiàn)[2]

  1. 編寫一個簡單的 GoLang 程序 main.go, 用 go build -o main main.go 編譯生成可執(zhí)行文件 main
import "fmt"
func main(){
    fmt.Println("Hello, World!")
}
  1. 使用 gdb main 命令調(diào)試 main 程序

  2. 輸入 info files 命令查看程序入口地址


  1. 用 break 命令給該地址打斷點,然后運(yùn)行程序。發(fā)現(xiàn)程序在 _rt0_amd64_linux 函數(shù)的 JMP _rt0_amd64(SB) 命令停下。
  2. _rt0_amd64_linux 就是入口函數(shù),該函數(shù)沒有做任何操作直接跳轉(zhuǎn)到 _rt0_amd64(SB),見 參考文獻(xiàn)[6]


  3. 繼續(xù)跳轉(zhuǎn)到 _rt0_amd64, 可以看到該函數(shù)將存放在棧內(nèi)的 argc 和 argv復(fù)制給參數(shù)寄存器 DI 和 SI 后又跳轉(zhuǎn)到 runtime·rt0_go(SB)
  4. 在 參考文獻(xiàn)[8] 中我們可以看到 GoLang 跑起來的主要代碼:



    (1) 使用 schedinit 始化堆棧,GC 等,根據(jù) GOMAXPROCS 設(shè)置創(chuàng)建 P 結(jié)構(gòu)的池子。參考文獻(xiàn)[10]
    (2) 創(chuàng)建一個新的 G,來執(zhí)行 runtime.mainPC 函數(shù)
    (3) 啟動當(dāng)前的 M, mstart 調(diào)用 schedule,開始執(zhí)行之前綁定的 mainPC 所在的 G

GoLang 的 GMP 模型
上一節(jié)提到來 G,M, P 到底是什么意思呢?

  • P(Processor): “處理器”,主要用來限制實際運(yùn)行的 M 的數(shù)量。默認(rèn)數(shù)量跟 CPU 的物理線程數(shù)一致,受 GOMAXPROCS 控制。 參考文獻(xiàn)[15]

  • M(Machine): OS Thread,由 OS 調(diào)度。M 的數(shù)量不一定。但是處于非阻塞狀態(tài)的 M 由 P 決定。
    M 和 P 的區(qū)別與聯(lián)系在于:P 是 GoLang 假想的“處理器”,控制實際能夠跑起來的 M 的數(shù)量。
    如,在 G 的數(shù)量無限多的情況下,一開始 M 和 P 的數(shù)量一樣多。但是當(dāng)運(yùn)行在 M 上的 G 調(diào)用了同步系統(tǒng)調(diào)用阻塞了 M, 此時這個 M 線程是沒辦法做其他操作的。
    但是 GoLang 想要運(yùn)行中的 M(OS Thread) 數(shù)量是和 P 的數(shù)量一樣多的,所以它會再創(chuàng)建一個 M(OS Thread) 來跑新的 G。這樣就能動態(tài)的保證“運(yùn)行中的 M 的數(shù)量等于 P 的總數(shù)”

  • G(GoRoutine):協(xié)程,應(yīng)用層看到的“線程”。由 M 調(diào)度和執(zhí)行。

  • LRQ(Local Run Queue): 從定義可知,P 相當(dāng)于是對 M 的約束,M 只有綁定來 P 才能實際調(diào)度和執(zhí)行 G。
    因此,GoLang 給每個 P 設(shè)置了一個 LRQ 來維護(hù)一個待執(zhí)行 G 的隊列。
    當(dāng)有 M 綁定在 P 上時,M 就會優(yōu)先調(diào)度和執(zhí)行該 P 的 LRQ 上的 G
    需要重點關(guān)注的一點是:M 才是 OS Thread,因此只有 M 才有執(zhí)行和調(diào)度 G 的能力。而 P 只是一個約束條件。

  • GRQ(Global Run Queue): 沒有綁定任何 P 的 G 就會被扔進(jìn) GRQ,等待被需要的 P-M 加入 LRQ 執(zhí)行。

P 的狀態(tài):

  • Pidle: means a P is not being used to run user code or the scheduler. Typically, it’s on the idle P list and available to the scheduler
  • Prunning: means a P is owned by an M and is being used to run user code or the scheduler.
  • Psyscall: means a P is not running user code, may be stolen by another M.
  • Pgcstop: means a P is halted for STW and owned by the M that stopped the world.
  • Pdead: means a P is no longer used (GOMAXPROCSshrank). We reuse Ps if GOMAXPROCS increases.
    G 的狀態(tài):
  • Gidle:just allocated and has not yet been initialized
  • Grunnable:this goroutine is on a run queue
  • Grunning:means this goroutine may execute user code
  • Gsyscall: means this goroutine is executing a system call
  • Gwaiting: means this goroutine is blocked in the runtime(channel)

GMP 的協(xié)作

  1. 一般調(diào)度:如圖,當(dāng) M 綁定 P 時,就會開始調(diào)度執(zhí)行 P 的 LRQ 中的 G。
    當(dāng)執(zhí)行 61 ticks,或者 LRQ 沒有 G 時,就會去 GRQ 或者其他的 P 的 LRQ 搶 G 來執(zhí)行。
    需要特別注意的是圖中 LRQ,G,M,P 和 Core 的相對位置關(guān)系。M 需要綁定 P 才能調(diào)度執(zhí)行 G, 因此 G 在它倆之間。


  1. 異步系統(tǒng)調(diào)用:當(dāng) G 需要執(zhí)行異步系統(tǒng)調(diào)用時,M 會把它扔給 NetPoller 管理,然后從 LRQ 中重新取一個 G 來處理。
    NetPoller 通過 epoll 等函數(shù),由監(jiān)控線程 sysmon 定期詢問。當(dāng)某個 G 的異步系統(tǒng)調(diào)用完成后,該 G 就會被扔進(jìn) GRQ 中,等待被再次調(diào)度。


  1. 同步系統(tǒng)調(diào)用:我們知道,同步系統(tǒng)調(diào)用阻塞了 M(OS Thread), 該 M 是沒有辦法為其他 G 提供服務(wù)的。此時,該 M 對 P 而言就是沒有意義的。(P 的意義是約束運(yùn)行中的 M 的個數(shù))
    所以,GoLang 會將阻塞的 M - G 和 P 解綁,然后給 P 創(chuàng)建/調(diào)度一個新的 M。這樣就能保證“運(yùn)行中的 M 的數(shù)量等于 P 的總數(shù)”。
    當(dāng)同步系統(tǒng)調(diào)用完成后,阻塞的 M 會將執(zhí)行系統(tǒng)調(diào)用的 G 還回 LRQ。然后該 M 等待銷毀或者被重新綁定給某個 P。


    image.png

幾個重要的函數(shù)
runtime.schedule 參考文獻(xiàn) [24]
(1)從 TLS 獲取當(dāng)前正在運(yùn)行的 G 的信息
(2)M 是否綁定到當(dāng)前的 G(同步系統(tǒng)調(diào)用)?M 讓出綁定的 P ,等待同步系統(tǒng)調(diào)用的 G 結(jié)束
(3)GC?STW(stop the word) for GC
(4)當(dāng)前 P 每執(zhí)行 61 ticks,從 GRQ 取一定量的 G 加入 LRQ 中
(5)從 LRQ 獲取可執(zhí)行的 G
(6)如果當(dāng)前的 LRQ 沒有 G, 則從 GRQ 或者其他 P 的 LRQ 搶 G 來調(diào)度執(zhí)行
(7)如果都沒有找到 G,當(dāng)前 M 讓出占用的 P, 進(jìn)入休眠狀態(tài)

runtime.mainPC(runtime.main) 參考文獻(xiàn)[20]

(1)限制最大棧大小: Max stack size is 1 GB on 64-bit, 250 MB on 32-bit
(2)創(chuàng)建一個不需要綁定 P 的 M,,執(zhí)行 sysmon 函數(shù)
(3)創(chuàng)建 GC goroutine,啟動 GC
(4)運(yùn)行 package main 的 main 函數(shù)
(5)一系列的收尾操作

runtime.sysmon 參考文獻(xiàn)[21]
(1)獲取 NetPoller 中已完成操作的 G,將其加入 GRQ 中
(2)retake:

搶占長時間運(yùn)行的 G
回收被 syscall 長時間阻塞的 P
————————————————
版權(quán)聲明:本文為CSDN博主「楊領(lǐng)well」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/yanglingwell/article/details/103538730

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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