Go运行时调度器精讲(四):main goroutine如何运行?
摘要:原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 皇天不负有心人,终于我们到了运行 main goroutine 环节了。让我们走起来,看看一个 goroutine 到底是怎么运行的。 1. 运行 goroutine 稍微回顾下前面的
原创文章,欢迎转载,转载请注明出处,谢谢。
0. 前言
皇天不负有心人,终于我们到了运行 main goroutine 环节了。让我们走起来,看看一个 goroutine 到底是怎么运行的。
1. 运行 goroutine
稍微回顾下前面的内容,第一讲 Go 程序初始化,介绍了 Go 程序是怎么进入到 runtime 的,随之揭开 runtime 的面纱。第二讲,介绍了调度器的初始化,要运行 goroutine 调度器是必不可少的,只有调度器准备就绪才能开始工作。第三讲,介绍了 main goroutine 是如何创建出来的,只有创建一个 goroutine 才能开始运行,否则执行代码无从谈起。这一讲,我们继续介绍如何运行 main goroutine。
我们知道 main goroutine 此时处于 _Grunnable 状态,要使得 main goroutine 处于 _Grunning 状态,还需要将它和 P 绑定。毕竟 P 是负责调度任务给线程处理的,只有和 P 绑定线程才能处理相应的 goroutine。
1.1 绑定 P
回到代码 newproc:
func newproc(fn *funcval) {
gp := getg()
pc := getcallerpc()
systemstack(func() {
newg := newproc1(fn, gp, pc) // 创建 newg,这里是 main goroutine
pp := getg().m.p.ptr() // 获取当前工作线程绑定的 P,这里是 g0.m.p = allp[0]
runqput(pp, newg, true) // 绑定 allp[0] 和 main goroutine
if mainStarted { // mainStarted 还未启动,这里是 false
wakep()
}
})
}
进入 runqput 函数查看 main goroutine 是怎么和 allp[0] 绑定的:
// runqput tries to put g on the local runnable queue.
// If next is false, runqput adds g to the tail of the runnable queue.
// If next is true, runqput puts g in the pp.runnext slot.
// If the run queue is full, runnext puts g on the global queue.
// Executed only by the owner P.
func runqput(pp *p, gp *g, next bool) {
...
if next {
retryNext:
oldnext := pp.runnext // 从 P 的 runnext 获取下一个将要执行的 goroutine,这里 pp.runnext = nil
if !pp.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) { // 将 P 的 runnext 更新为 gp,这里的 gp 是 main goroutine
goto retryNext
}
if oldnext == 0 { // 如果 P 原来要执行的 goroutine 是 nil,则直接返回,这里创建的是 main goroutine 将直接返回
return
}
gp = oldnext.ptr() // 如果不为 nil,表示是一个将要执行的 goroutine。
