专栏首页一起学GolangGo调度器系列(4)源码阅读与探索

Go调度器系列(4)源码阅读与探索

各位朋友,这次想跟大家分享一下Go调度器源码阅读相关的知识和经验,网络上已经有很多剖析源码的好文章,所以这篇文章不是又一篇源码剖析文章,注重的不是源码分析分享,而是带给大家一些学习经验,希望大家能更好的阅读和掌握Go调度器的实现

本文主要分2个部分:

  1. 解决如何阅读源码的问题。阅读源码本质是把脑海里已经有的调度设计,看看到底是不是这么实现的,是怎么实现的。
  2. 带给你一个探索Go调度器实现的办法。源码都到手了,你可以修改、窥探,通过这种方式解决阅读源码过程中的疑问,验证一些想法。比如:负责调度的是g0,怎么才能schedule()在执行时,当前是g0呢?

阅读源码

阅读前提

阅读Go源码前,最好已经掌握Go调度器的设计和原理。如果你还无法回答以下问题:

  1. 为什么需要Go调度器?
  2. Go调度器与系统调度器有什么区别和关系/联系?
  3. G、P、M是什么,三者的关系是什么?
  4. P有默认几个?
  5. M同时能绑定几个P?
  6. M怎么获得G?
  7. M没有G怎么办?
  8. 为什么需要全局G队列?
  9. Go调度器中的负载均衡的2种方式是什么?
  10. work stealing是什么?什么原理?
  11. 系统调用对G、P、M有什么影响?
  12. Go调度器抢占是什么样的?一定能抢占成功吗?

建议阅读Go调度器系列文章,以及文章中的参考资料:

  1. Go调度器系列(1)起源
  2. Go调度器系列(2)宏观看调度器
  3. Go调度器系列(3)图解调度原理

优秀源码资料推荐

既然你已经能回答以上问题,说明你对Go调度器的设计已经有了一定的掌握,关于Go调度器源码的优秀资料已经有很多,我这里推荐2个:

  1. 雨痕的Go源码剖析六章并发调度,不止是源码,是以源码为基础进行了详细的Go调度器介绍:ttps://github.com/qyuhen/book
  2. Go夜读第12期,golang中goroutine的调度,M、P、G各自的一生状态,以及转换关系:https://reading.developerlearning.cn/reading/12-2018-08-02-goroutine-gpm/

Go调度器的源码还涉及GC等,阅读源码时,可以暂时先跳过,主抓调度的逻辑。

另外,Go调度器涉及汇编,也许你不懂汇编,不用担心,雨痕的文章对汇编部分有进行解释。

最后,送大家一幅流程图,画出了主要的调度流程,大家也可边阅读边画,增加理解,高清版可到博客下载(原图原文跳转)

如何探索调度器

这部分教你探索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
  1. 初次编译,会跑测试,耗时长一点
$ cd $GODIR/src
$ ./all.bash
  1. 以后每次修改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
  1. 为了防止我们修改的go和过去安装的go冲突,创建igo软连接,指向修改的go。
$ mkdir -p ~/testgo/bin
$ cd ~/testgo/bin
$ ln -sf $GODIR/bin/go igo
  1. 最后,把~/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.schedtraceprint()函数可以用作打印调试信息,那我们是不是可以使用这种方法打印我们想获取的信息呢?当然可以。

另外,注意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调度器的时候,能多一些自己的探索和研究,而不仅仅停留在看看别人文章之上

参考资料

  1. Installing Go from source

本文分享自微信公众号 - 一起学Golang(golang_together)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • google的GCM推送使用简介

    转载请注明出处:http://blog.csdn.net/newhope1106/article/details/54709916

    爱撸猫的杰
  • go语言基础11-channel

    吐吐吐吐吐葡萄皮
  • web站点之路——公安备案篇

    一般在ICP备案完成的30天内,就可以进行公安备案,现在还没有强制性的要求公安备案,只对在大陆的网站有ICP备案的强制性要求。公安备案的地址为:ht...

    相柳
  • go语言基础8-函数式编程

    吐吐吐吐吐葡萄皮
  • 可视化实时Web日志分析工具,堪称神器!

    说到web服务器就不得不说Nginx,目前已成为企业建站的首选。但由于种种历史原因,Nginx日志分析工具相较于传统的apache、lighthttp等还是少很...

    猿哥
  • Golang须知知识点

    (1)同级目录不能放多个包,否则报编译错误; (2)实现一个接口需要实现接口中的所有方法。 (3)Golang中根据首字母的大小写来确定可以访问的权限。方法...

    Dabelv
  • Golang交叉编译

    Golang 支持交叉编译,在一个平台上生成另一个平台的可执行程序,最近使用了一下,非常好用,这里备忘一下。

    _simple
  • Go、Nginx、Php、Nodejs谁能胜出紫禁之巅

    由于我们在项目中大量使用Iris和nginx这两个Web框架(纯Go语言实现,零内存拷贝),同时也听过很多人问Go的Http性能对比nginx、对比nodejs...

    猿哥
  • 大众美团服务链监控CAT

    CAT 作为服务端项目基础组件,提供了 Java, C/C++, Node.js, Python, Go 等多语言客户端,已经在美团点评的基础架构中间件框架(M...

    爱撸猫的杰
  • 结合使用 Draft 与 Tencent Kubernetes Engine (TKE)

    Draft 是一种开源工具,有助于在 Kubernetes 群集中打包和部署应用程序容器,让你专注于开发周期 - 专注开发的“内部循环”。 在开发代码期间,但尚...

    张善友

扫码关注云+社区

领取腾讯云代金券