Go runtime 调度器非 main goroutine 运行如何为?

摘要:原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 在 Go runtime 调度器精讲(三):main goroutine 创建 介绍了 main goroutine 的创建,文中我们说 main goroutine 和非 main
原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 在 Go runtime 调度器精讲(三):main goroutine 创建 介绍了 main goroutine 的创建,文中我们说 main goroutine 和非 main goroutine 有区别。当时卖了个关子并未往下讲,这一讲我们会继续介绍非 main goroutine (也就是 go 关键字创建的 goroutine,后文统称为 gp) 的运行,并且把这个关子解开,说一说它们的区别在哪儿。 1. gp 的创建 首先看一个示例: func g2() { time.Sleep(10 * time.Second) println("hello world") } func main() { go g2() time.Sleep(1 * time.Minute) println("main exit") } main 函数创建两个 goroutine,一个 main goroutine,一个普通 goroutine。从 Go runtime 调度器精讲(四):运行 main goroutine 可知 main goroutine 运行完之后就调用 exit(0) 退出了。为了能进入 gp,我们这里在 main goroutine 中加了 1 分钟的等待时间。 Go runtime 的启动在前几讲都有介绍,这里直接进入 main 函数,查看 gp 是如何创建的: (dlv) c > main.main() ./goexit.go:12 (hits goroutine(1):1 total:1) (PC: 0x46238a) 7: func g2() { 8: time.Sleep(10 * time.Second) 9: println("hello world") 10: } 11: => 12: func main() { 13: go g2() 14: 15: time.Sleep(30 * time.Minute) 16: println("main exit") 17: } 直接看 main 函数,我们看不出 go 关键字做了什么,查看 CPU 的汇编指令: (dlv) si > main.main() ./goexit.go:13 (PC: 0x462395) goexit.go:12 0x462384 7645 jbe 0x4623cb goexit.go:12 0x462386 55 push rbp goexit.go:12 0x462387 4889e5 mov rbp, rsp goexit.go:12 0x46238a* 4883ec10 sub rsp, 0x10 goexit.go:13 0x46238e 488d050b7a0100 lea rax, ptr [rip+0x17a0b] => goexit.go:13 0x462395 e8c6b1fdff call $runtime.newproc goexit.go:15 0x46239a 48b800505c18a3010000 mov rax, 0x1a3185c5000 goexit.go:15 0x4623a4 e8b79fffff call $time.Sleep 可以看到,go 关键字被编译转换后实际调用的是 $runtime.newproc 函数,这个函数在 Go runtime 调度器精讲(四):运行 main goroutine 已经非常详细的介绍过了,这里就不赘述了。 有必要在说明的是,main goroutine 和普通 goroutine 执行的顺序。当调用 runtime.newproc 后,gp 被添加到 P 的可运行队列(如果队列满,被添加到全局队列),接着线程会调度运行该 gp。
阅读全文