您現在的位置是:首頁 > 藝術

Go排程器系列(4)Go原始碼如何閱讀?本文帶你進入Go原始碼的世界

由 Go中國 發表于 藝術2022-07-12
簡介驗證schedule()由g0執行閱讀原始碼的文章,你已經知道了g0是負責排程的,並且g0是全域性變數,可在runtime包的任何地方直接使用,看到schedule()程式碼如下(所在檔案:$GODIRsrcruntimeproc

origin可以畫流程圖嗎

Go語言中文網,致力於每日分享編碼、開源等知識,歡迎關注我,會有意想不到的收穫!

各位朋友,這次想跟大家分享一下Go排程器原始碼閱讀相關的知識和經驗,網路上已經有很多剖析原始碼的好文章,所以這篇文章

不是又一篇原始碼剖析文章,注重的不是原始碼分析分享,而是帶給大家一些學習經驗,希望大家能更好的閱讀和掌握Go排程器的實現

本文主要分2個部分:

解決如何閱讀原始碼的問題

。閱讀原始碼本質是把腦海裡已經有的排程設計,看看到底是不是這麼實現的,是怎麼實現的。

帶給你一個探索Go排程器實現的辦法

。原始碼都到手了,你可以修改、

窺探

,透過這種方式解決閱讀原始碼過程中的疑問,驗證一些想法。比如:負責排程的是g0,怎麼才能schedule()在執行時,當前是g0呢?

01 如何閱讀原始碼

閱讀前提

閱讀Go原始碼前,最好已經掌握Go排程器的設計和原理,如果你還無法回答以下問題:

為什麼需要Go排程器?

Go排程器與系統排程器有什麼區別和關係/聯絡?

G、P、M是什麼,三者的關係是什麼?

P預設有幾個?

M同時能繫結幾個P?

M怎麼獲得G?

M沒有G怎麼辦?

為什麼需要全域性G佇列?

Go排程器中的負載均衡的2種方式是什麼?

work stealing是什麼?什麼原理?

系統呼叫對G、P、M有什麼影響?

Go排程器搶佔是什麼樣的?一定能搶佔成功嗎?

建議閱讀Go排程器系列文章,以及文章中的參考資料:

Go排程器系列(1)起源

Go排程器系列(2)宏觀看排程器

Go排程器系列(3)圖解排程原理

優秀原始碼資料推薦

既然你已經能回答以上問題,說明你對Go排程器的設計已經有了一定的掌握,關於Go排程器原始碼的優秀資料已經有很多,我這裡推薦2個:

雨痕的Go原始碼剖析

六章併發排程,不止是原始碼,是以原始碼為基礎進行了詳細的Go排程器介紹:ttps://github。com/qyuhen/book

Go夜讀

第12期,golang中goroutine的排程,M、P、G各自的一生狀態,以及轉換關係:https://reading。developerlearning。cn/reading/12-2018-08-02-goroutine-gpm/

Go排程器的原始碼還涉及GC等,閱讀原始碼時,可以暫時先跳過,主抓排程的邏輯。

另外,Go排程器涉及彙編,也許你不懂彙編,不用擔心,雨痕的文章對彙編部分有進行解釋。

最後,送大家一幅流程圖,畫出了主要的排程流程,大家也可邊閱讀邊畫,增加理解,

高畫質版可到部落格下載(原圖原文跳轉)

Go排程器系列(4)Go原始碼如何閱讀?本文帶你進入Go原始碼的世界

02 如何探索排程器

這部分教你探索Go排程器的原始碼,驗證想法,主要思想就是,下載Go的原始碼,新增除錯列印,編譯修改的原始檔,生成修改的go,然後使用修改go執行測試程式碼,觀察結果。

下載和編譯Go

1。Github下載,並且換到go1。11。2分支,本文所有程式碼修改都基於go1。11。2版本。

$ GODIR=$GOPATH/src/github。com/golang/go

$ mkdir -p $GODIR

$ cd $GODIR/。。

$ git clone https://github。com/golang/go。git

$ cd go

$ git fetch origin go1。11。2

$ git checkout origin/go1。11。2

$ git checkout -b go1。11。2

$ git checkout go1。11。2

2。 初次編譯,會跑測試,耗時長一點

$ cd $GODIR/src

$ 。/all。bash

3。以後每次修改go原始碼後可以這樣,4分鐘左右可以編譯完成

$ cd $GODIR/src

$ time 。/make。bash

Building Go cmd/dist using /usr/local/go。

Building Go toolchain1 using /usr/local/go。

Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1。

Building Go toolchain2 using go_bootstrap and Go toolchain1。

Building Go toolchain3 using go_bootstrap and Go toolchain2。

Building packages and commands for linux/amd64。

——-

Installed Go for linux/amd64 in /home/xxx/go/src/github。com/golang/go

Installed commands in /home/xxx/go/src/github。com/golang/go/bin

real 1m11。675s

user 4m4。464s

sys 0m18。312s

編譯好的go和gofmt在$GODIR/bin目錄。

$ ll $GODIR/bin

total 16044

-rwxrwxr-x 1 vnt vnt 13049123 Apr 14 10:53 go

-rwxrwxr-x 1 vnt vnt 3377614 Apr 14 10:53 gofmt

4。為了防止我們修改的go和過去安裝的go衝突,建立igo軟連線,指向修改的go。

$ mkdir -p ~/testgo/bin

$ cd ~/testgo/bin

$ ln -sf $GODIR/bin/go igo

5。最後,把~/testgo/bin加入到PATH,就能使用igo來編譯程式碼了,執行下igo,應當獲得go1。11。2的版本:

$ igo version

go version go1。11。2 linux/amd64

當前,已經掌握編譯和使用修改的go的辦法,接下來就以1個簡單的例子,教大家如何驗證想法。

驗證schedule()由g0執行

閱讀原始碼的文章,你已經知道了g0是負責排程的,並且g0是全域性變數,可在runtime包的任何地方直接使用,看到schedule()程式碼如下(所在檔案:$GODIR/src/runtime/proc。go):

// One round of scheduler: find a runnable goroutine and execute it。

// Never returns。

func schedule() {

// 獲取當前g,排程時這個g應當是g0

_g_ := getg()

if _g_。m。locks != 0 {

throw(“schedule: holding locks”)

}

// m已經被某個g鎖定,先停止當前m,等待g可執行時,再執行g,並且還得到了g所在的p

if _g_。m。lockedg != 0 {

stoplockedm()

execute(_g_。m。lockedg。ptr(), false) // Never returns。

}

// 省略。。。

}

問題

:既然g0是負責排程的,為何schedule()每次還都執行_g_ := getg(),直接使用g0不行嗎?schedule()真的是g0執行的嗎?

在Go排程器系列(2)宏觀看排程器這篇文章中我曾介紹了trace的用法,閱讀程式碼時發現

使用debug.schedtrace和print()函式可以用作列印除錯資訊

,那我們是不是可以使用這種方法列印我們想獲取的資訊呢?當然可以。

另外,注意print()並不是fmt。Print(),也不是C語言的printf,所以不是格式化輸出,它是彙編實現的,我們不深入去了解它的實現了,現在要掌握它的用法:

// The print built-in function formats its arguments in an

// implementation-specific way and writes the result to standard error。

// Print is useful for bootstrapping and debugging; it is not guaranteed

// to stay in the language。

func print(args 。。。Type)

從上面可以看到,它接受可變長引數,我們使用的時候只需要傳進去即可,但要手動控制格式。

我們修改schedule()函式,使用debug。schedtrace > 0控制列印,加入3行程式碼,把goid給打印出來,如果始終列印goid為0,則代表排程確實是由g0執行的:

if debug。schedtrace > 0 {

print(“schedule(): goid = ”, _g_。goid, “\n”) // 會是0嗎?是的

}

schedule()如下:

// One round of scheduler: find a runnable goroutine and execute it。

// Never returns。

func schedule() {

// 獲取當前g,排程時這個g應當是g0

_g_ := getg()

if debug。schedtrace > 0 {

print(“schedule(): goid = ”, _g_。goid, “\n”) // 會是0嗎?是的

}

if _g_。m。locks != 0 {

throw(“schedule: holding locks”)

}

// 。。。

}

編譯igo:

$ cd $GODIR/src

$ 。/make。bash

編寫一個簡單的demo(不能更簡單):

package main

func main() {

}

結果如下,你會發現所有的schedule()函式呼叫都列印goid = 0,足以證明Go排程器的排程由g0完成(如果你認為還是缺乏說服力,可以寫複雜一些的demo):

複製

$ GODEBUG=schedtrace=1000 igo run demo1。go

schedule(): goid = 0

schedule(): goid = 0

SCHED 0ms: gomaxprocs=8 idleprocs=6 threads=4 spinningthreads=1 idlethreads=0 runqueue=0 [0 0 0 0 0 0 0 0]

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

schedule(): goid = 0

// 省略幾百行

啟發比結論更重要,希望各位朋友在學習Go排程器的時候,能多一些自己的探索和研究,而不僅僅停留在看看別人文章之上

參考資料

Installing Go from source

如果這篇文章對你有幫助,不妨關注下我的Github,有文章會收到通知。本文作者:大彬,原創授權釋出如果喜歡本文,隨意轉載,但請保留此原文連結:http://lessisbetter。site/2019/04/14/golang-scheduler-4-explore-source-code/

推薦文章