聊聊 g0

不少時候,當咱們跟着源碼去理解某種事物時,基本上能夠認爲是以時間順序展開,這是編年體的邏輯。還有另外一種邏輯,紀傳體,它以人物爲中心編排史事,使得讀者更聚焦於某我的物。以一種新的視角,把全部的事情串連起來,使人大呼過癮。今天咱們試着以這樣一種邏輯再看 g0。linux

回顧一下 Go 夜讀第 78 期,關於調度器源碼分析的內容。咱們講過,與主線程綁定的 M 對應的 g0 的主要做用是提供一個比通常 goroutine 要大的多棧(64K)供 runtime 代碼執行。golang

初始化的過程當中,在函數 runtime·rt0_go 裏會給主線程的 g0 分配棧空間:函數

g0 棧空間

以後,主線程會與 m0 綁定,m0 又與 g0 綁定:源碼分析

主線程綁定 m0,g0

以後,又與 p0 綁定:atom

g0-p0-m0

這樣,主線程的這一套 GPM 就能夠轉起來了。接着,就建立了 main goroutine,放入 p0 的本地待運行隊列。最後,經過 schedule() 函數進入調度循環。線程

前面說的是程序初始化的過程當中,g0 是如何誕生的。當執行到 main.main() 函數,也說是用戶在 main 包下寫的 main 函數裏,咱們隨手一句:code

go func() {
    // 要作的事
}()

就啓動了一個 goroutine 時,在 Go 編譯器的做用下,最終會轉化成 newproc 函數。在 newproc 函數的內部,會在 g0 棧上調用 newproc1 函數,完成後續的工做。建立完成後,會將新建立的 goroutine 放入 _p_ 的本地待運行隊列。隊列

由於新增長了一個 g,這時會嘗試去喚醒一個 P 來一塊兒執行任務。判斷條件是:進程

if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {
	wakep()
}

即在有空閒 P 以及沒有正在「找工做的 M」的狀況下,纔會嘗試去喚醒一個 P。咱們又知道,其實 P 的數量在程序運行過程當中通常不會變化,因此這裏所謂的喚醒其實就是把空閒的 P 利用起來。ip

經過 wakep() -> startm() -> newm() -> allocm() -> malg() 這條鏈路建立 g0,這裏 g0 的棧大小實際上爲 8KB

mp.g0 = malg(8192 * sys.StackGuardMultiplier) // sys.StackGuardMultiplier 在 linux 裏爲 1

g0 做爲一個特殊的 goroutine,爲 scheduler 執行調度循環提供了場地(棧)。對於一個線程來講,g0 老是它第一個建立的 goroutine。以後,它會不斷地尋找其餘普通的 goroutine 來執行,直到進程退出。

當須要執行一些任務,且不想擴棧時,就能夠用到 g0 了,由於 g0 的棧比較大。g0 其餘的一些「職責」有:建立 goroutine、deferproc 函數裏新建 _defer、垃圾回收相關的工做(例如 stw、掃描 goroutine 的執行棧、一些標識清掃的工做、棧增加)等等。

由於 g0 這樣一個特殊的 goroutine 所作的工做,使得 Go 程序運行地更快。

注:最近在 medium 上看到了一個很是讚的關於 Go 的博客,題圖畫得頗有閱讀的慾望。這篇文章也是參考於其中的一篇

相關文章
相關標籤/搜索