我们上篇文章( Golang 的底层引导流程/启动顺序)介绍了一个golang程序的启动流程,在文章的最后对于最重要的一点“调度
“ (函数 [schedule()](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go#L2607-L2723)
) 并没有展开来讲,今天我们继续从源码来分析一下它的调度机制。
在此之前我们要明白golang中的调度主要指的是什么?在 src/runtime/proc.go
文件里有一段注释这样写到
// Goroutine scheduler
// The scheduler’s job is to distribute ready-to-run goroutines over worker threads.
这里指如何找一个已准备好运行的 G 关联到PM 让其执行。对于G 的调度可以围绕三个方面来理解:
- 时机:什么时候关联(调度)。对于调度时机一般是指有空闲P的时候都会去找G执行
- 对象:选择哪个G进行调度。这是我们本篇要讲的内容
- 机制:如何调度。
execute()
函数
理解了这三个问题,基本也就明白了它的调度策略了,本篇主要对G的获取。
源文件 [src/runtime/proc.go](https://github.com/golang/go/blob/go1.15.6/src/runtime/proc.go)
, go version 1.15.6
获取G流程
下面我们看一下 schedule()
函数
// One round of scheduler: find a runnable goroutine and execute it.
// Never returns.
func schedule() {}
从注释我们可得知以下几点:
- 调度是一轮一轮执行的,并不是只执行一次
- 调度的工作就是找到一个
runnable
状态的G,对于G的选择可直接理解为调度(实际上这只是调试中的前半部分,找到G 还要把它放在一个位置去执行),至少大部分场景下我们谈论的是指G的调度 - 当前这个调度并无返回值,也就是说在当函数执行结束时就代表当前一轮的调度已结束(不严谨)。剩下的执行此函数后面的程序
func schedule() {
// 获取当前 g0
_g_ := getg()
if _g_.m.locks != 0 {
throw("schedule: holding locks")
}
if _g_.m.lockedg != 0 {
stoplockedm()
execute(_g_.m.lockedg.ptr(), false) // Never returns.
}
// We should not schedule away from a g that is executing a cgo call,
// since the cgo call is using the m's g0 stack.
// 不允许cgo正在使用g0栈
if _g_.m.incgo {
throw("schedule: in cgo")
}
top:
pp := _g_.m.p.ptr()
pp.preempt = false
if sched.gcwaiting != 0 {
gcstopm()
goto top
}
if pp.runSafePointFn != 0 {
runSafePointFn()
}
// Sanity check: if we are spinning, the run queue should be empty.
// 健全性检查:如果有m正在spinning 的话,则g的运行队列应该是空的才对,这个应该很好理解
// Check this before calling checkTimers, as that might call
// goready to put a ready goroutine on the local run queue.
// 健全性检查必须要在调用 checkTimers() 之前进行检查,这是因为有可能goready()在本地运行队列放了一个就绪的goroutine
if _g_.m.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
throw("schedule: spinning with local work")
}
// 重点关注 定时器处理
checkTimers(pp, 0)
var gp *g
var inheritTime bool
// Normal goroutines will check for need to wakeP in ready,
// but GCworkers and tracereaders will not, so the check must
// be done here instead.
tryWakeP := false
if trace.enabled || trace.shutdown {
gp = traceReader()
if gp != nil {
casgstatus(gp, _Gwaiting, _Grunnable)
traceGoUnpark(gp, 0)
tryWakeP = true
}
}
if gp == nil && gcBlackenEnabled != 0 {
gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
tryWakeP = tryWakeP || gp != nil
}
if gp == nil {
// Check the global runnable queue once in a while to ensure fairness.
// Otherwise two goroutines can completely occupy the local runqueue
// by constantly respawning each other.
// 为了确保公平性,调度器会每经过61次调度就直接从全局g运行队列获取1个G,否则直接从本地g运行队列获取
if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
lock(&sched.lock)
gp = globrunqget(_g_.m.p.ptr(), 1)
unlock(&sched.lock)
}
}
// 从与当前m关联的p本地g队列获取,优先读取p.runnext的G,如果为nil再从队列里取
if gp == nil {
gp, inheritTime = runqget(_g_.m.p.ptr())
// We can see gp != nil here even if the M is spinning,
// if checkTimers added a local goroutine via goready.
}
// 以阻塞方式获取一个g, 重点关注函数 finrunnable() 函数
if gp == nil {
gp, inheritTime = findrunnable() // blocks until work is available
}
// This thread is going to run a goroutine and is not spinning anymore,
// so if it was marked as spinning we need to reset it now and potentially
// start a new spinning M.
// 如果当前m处于 spinning 状态,则进行重置,因为它要执行当前获取到的G
if _g_.m.spinning {
resetspinning()
}
if sched.disable.user && !schedEnabled(gp) {
// Scheduling of this goroutine is disabled. Put it on
// the list of pending runnable goroutines for when we
// re-enable user scheduling and look again.
lock(&sched.lock)
if schedEnabled(gp) {
// Something re-enabled scheduling while we
// were acquiring the lock.
unlock(&sched.lock)
} else {
sched.disable.runnable.pushBack(gp)
sched.disable.n++
unlock(&sched.lock)
goto top
}
}
// If about to schedule a not-normal goroutine (a GCworker or tracereader),
// wake a P if there is one.
// 如果调度的是一个gcworker或traceeader,则唤醒一个P (gc也是并发的)
if tryWakeP {
wakep()
}
if gp.lockedm != 0 {
// Hands off own p to the locked m,
// then blocks waiting for a new p.
startlockedm(gp)
goto top
}
// 执行G(将G运行在一个m上)
execute(gp, inheritTime)
}
可以看到,整个 schedue()
函数主要工作就是获取一个 _Grunnable
状态的G,大概获取流程如下