go语言调度gmp原理(4)
go语言调度gmp原理(4)
触发调度
下面简单介绍所有触发调度的时间点。因为调度器的runtime.schedule会重新选择goroutine在线程上执行,所以我们只要找到该函数的调用方,就能找到所有触发调度的时间点
这里重点介绍运行时触发调度的几条路径
- 主动挂起——runtime.gopark -> runtime.park_m
- 系统调用——runtime.exitsyscall -> runtime.exitsyscall0
- 协作式调度——runtime.Gosched -> runtime.gosched_m -> runtime.goschedIpml
- 系统监控——runtime.sysmon -> runtime.retake -> runtime.preemptone
这里介绍的调度时间点不是将线程的运行权直接交给其他任务,而是通过调度器的runtime.schedule重新调度。
主动挂起
runtime.gopark是触发调度最常用的方法,该函数会将当前goroutine暂停,暂停的任务不会放回运行队列。下面分析该函数的实现原理
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
if reason != waitReasonSleep {
checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
}
mp := acquirem()
gp := mp.curg
status := readgstatus(gp)
if status != _Grunning && status != _Gscanrunning {
throw("gopark: bad g status")
}
mp.waitlock = lock
mp.waitunlockf = unlockf
gp.waitreason = reason
mp.waittraceev = traceEv
mp.waittraceskip = traceskip
releasem(mp)
// can't do anything that might move the G between Ms here.
mcall(park_m)
}
上述函数会通过runtime.mcall切换到g0的栈上调用runtime.park_m
func park_m(gp *g) {
mp := getg().m
if trace.enabled {
traceGoPark(mp.waittraceev, mp.waittraceskip)
}
// N.B. Not using casGToWaiting here because the waitreason is
// set by park_m's caller.
casgstatus(gp, _Grunning, _Gwaiting)
dropg()
if fn := mp.waitunlockf; fn != nil {
ok := fn(gp, mp.waitlock)
mp.waitunlockf = nil
mp.waitlock = nil
if !ok {
if trace.enabled {
traceGoUnpark(gp, 2)
}
casgstatus(gp, _Gwaiting, _Grunnable)
execute(gp, true) // Schedule it back, never returns.
}
}
schedule()
}
runtime.park_m会将当前goroutine的状态从_Grunning切换至_Gwaiting,调用runtime.dropg移除线程和goroutine之间的关联,此后就可以调用runtime.schedule触发新一轮调度了
当goroutine等待的特定条件满足后,运行时会调用runtime.goready将因调用runtime.gopark而陷入休眠的goroutine唤醒
func goready(gp *g, traceskip int) {
systemstack(func() {
ready(gp, traceskip, true)
})
}
func ready(gp *g, traceskip int, next bool) {
if trace.enabled {
traceGoUnpark(gp, traceskip)
}
status := readgstatus(gp)
// Mark runnable.
mp := acquirem() // disable preemption because it can be holding p in a local var
if status&^_Gscan != _Gwaiting {
dumpgstatus(gp)
throw("bad g->status in ready")
}
// status is Gwaiting or Gscanwaiting, make Grunnable and put on runq
casgstatus(gp, _Gwaiting, _Grunnable)
runqput(mp.p.ptr(), gp, next)
wakep() //根据条件唤醒新的p
releasem(mp) //解除禁止抢占
}
runtime.ready会将准备就绪的goroutine的状态切换至_Grunnable,并将其加入处理器的运行队列中,等待调度器的调度