各位朋友,此次想跟你們分享一下Go調度器源碼閱讀相關的知識和經驗,網絡上已經有不少剖析源碼的好文章,因此這篇文章不是又一篇源碼剖析文章,注重的不是源碼分析分享,而是帶給你們一些學習經驗,但願你們能更好的閱讀和掌握Go調度器的實現。linux
本文主要分2個部分:git
schedule()
在執行時,當前是g0呢?閱讀Go源碼前,最好已經掌握Go調度器的設計和原理,若是你還沒法回答如下問題:github
建議閱讀Go調度器系列文章,以及文章中的參考資料:golang
既然你已經能回答以上問題,說明你對Go調度器的設計已經有了必定的掌握,關於Go調度器源碼的優秀資料已經有不少,我這裏推薦2個:bootstrap
Go調度器的源碼還涉及GC等,閱讀源碼時,能夠暫時先跳過,主抓調度的邏輯。bash
另外,Go調度器涉及彙編,也許你不懂彙編,不用擔憂,雨痕的文章對彙編部分有進行解釋。網絡
最後,送你們一幅流程圖,畫出了主要的調度流程,你們也可邊閱讀邊畫,增長理解,高清版可到博客下載(原圖原文跳轉)。併發
這部分教你探索Go調度器的源碼,驗證想法,主要思想就是,下載Go的源碼,添加調試打印,編譯修改的源文件,生成修改的go,而後使用修改go運行測試代碼,觀察結果。app
$ 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
$ cd $GODIR/src $ ./all.bash
$ 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
$ mkdir -p ~/testgo/bin $ cd ~/testgo/bin $ ln -sf $GODIR/bin/go igo
~/testgo/bin
加入到PATH
,就能使用igo
來編譯代碼了,運行下igo,應當得到go1.11.2的版本:$ igo version go version go1.11.2 linux/amd64
當前,已經掌握編譯和使用修改的go的辦法,接下來就以1個簡單的例子,教你們如何驗證想法。
閱讀源碼的文章,你已經知道了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調度器的時候,能多一些本身的探索和研究,而不只僅停留在看看別人文章之上。
- 若是這篇文章對你有幫助,請點個贊/喜歡,感謝。
- 本文做者:大彬
- 若是喜歡本文,隨意轉載,但請保留此原文連接:http://lessisbetter.site/2019...