各位朋友,此次想跟你们分享一下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...