继续看g
,在上一章中我们了解了golang
如何解决g
中代码存在系统调用的问题,接下来继续看另外一种情况,当g
中存在锁或者类似需要waiting
状态的时候,需要怎么处理?
其实简单想一想也能明白,无非是将
g
暂时挂起,让p
继续调度其他g
,等g
执行完毕之后,再将g
加入待运行队列即可,不过这里我们还是通过代码分析,补全我们的g
状态流程图。
gopark()
当g
需要等待某个条件达成时,例如sync.Lock、sync.WaitGroup
等,则会调用gopark()
,其源码如下:
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
}
//锁定当前m,禁止抢占
mp := acquirem()
//获取当前运行的g
gp := mp.curg
//检查当前g的状态
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)
//通过mcall调用 park_m
mcall(park_m)
}
mcall
的主要作用是切换到g0
执行传入的func()
,一般用于当前g
不需要继续执行,一般传入的func()
最终都会通过调用schedule()
结束,也就是这个方法不会再返回。继续看park_m()
func park_m(gp *g) {
//获取当前的g
_g_ := getg()
if trace.enabled {
traceGoPark(_g_.m.waittraceev, _g_.m.waittraceskip)
}
//将g的状态修改为_Gwaiting
casgstatus(gp, _Grunning, _Gwaiting)
//解除g与m的绑定
dropg()
//如果需要执行waitunlockf
if fn := _g_.m.waitunlockf; fn != nil {
//执行相关方法
ok := fn(gp, _g_.m.waitlock)
_g_.m.waitunlockf = nil
_g_.m.waitlock = nil
//如果waitunlockf 返回ok,则g不休眠,直接继续运行
if !ok {
if trace.enabled {
traceGoUnpark(gp, 2)
}
casgstatus(gp, _Gwaiting, _Grunnable)
execute(gp, true) // Schedule it back, never returns.
}
}
//继续执行调度
schedule()
}
可以看到park_m
和我们预想的一样,就是简单的把g
和m
解绑,然后将g
的状态修改为_Gwaiting
,然后继续调度其他g
.
goready()
当某个条件达成时,则可以调用gpready()
将对应的g
恢复运行,其具体原理是修改g
的状态,并将其加入运行队列,等待调度即可。
//runtime.proc.go
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)
}
//检查当前g的状态
status := readgstatus(gp)
_g_ := getg()
mp := acquirem()
if status&^_Gscan != _Gwaiting {
dumpgstatus(gp)
throw("bad g->status in ready")
}
//修改状态为_Grunnable
casgstatus(gp, _Gwaiting, _Grunnable)
//放进p的待运行队列
runqput(_g_.m.p.ptr(), gp, next)
//尝试唤醒m
wakep()
releasem(mp)
}
至此,我们可以继续补充g
的状态图:
从这里可以看出来Waiting
和Syscall
的区别在于Waiting
是主动挂起,而Syscall
是被动阻塞,主动挂起不需要占用m
,因此可以直接将自己踢出队列即可,而被动阻塞则需要处理m
与p
的关系,在调用前需要解绑p
,避免阻塞p
的其他g
的执行,调用完毕之后,需要再去寻找p
进行绑定,以便继续调度。
到这里,和调度相关的GMP
模型中的G
已经分析完毕,对于G
相关的问题我们也基本完全解答。后面几篇中,我们将继续分析M
和P
的状态变化。