本文需要关注如下两个问题:
-
如何唤醒睡眠中的工作线程?
-
如何创建新的工作线程?
上篇文章聊到ready通过将需唤醒的goroutine放入运行队列来唤醒它,本文接着分析。
为充分利用CPU,ready在唤醒goroutine之后会验证是否需要创建新的工作线程来工作,验证规则就是,如果当前有空闲的p而没有工作线程正在尝试从各个工作线程本地运行队列偷取goroutine(没有spinning状态的工作线程)的话,就需将空闲的p叫起来工作,来看runtime/proc.go文件639行代码分析ready:
// Mark gp ready to run.func ready(gp *g, traceskip int, next bool) {......// Mark runnable._g_ := getg()......// status is Gwaiting or Gscanwaiting, make Grunnable and put on runqcasgstatus(gp, _Gwaiting, _Grunnable)runqput(_g_.m.p.ptr(), gp, next) //放入运行队列if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 {//有空闲的p而且没有正在偷取goroutine的工作线程,则需要唤醒p出来工作wakep()}......}
唤醒空闲p是由wakep完成,来看runtime/proc.go文件2051行代码:
// Tries to add one more P to execute G's.// Called when a G is made runnable (newproc, ready).func wakep() {// be conservative about spinning threadsif !atomic.Cas(&sched.nmspinning, 0, 1) {return}startm(nil, true)}
wakep先通过cas操作再次确认是否有其它工作线程处于spinning状态,之所以要使用cas操作再次确认,就是因为当前工作线程是通过【atomic.Load(&sched.npidle)!= 0 && atomic.Load(&sched.nmspinning) == 0】这个条件来判断启动工作线程之后到真正启动工作线程之前这段时间内是否有工作线程进入spinning状态,正在四处寻找需要运行的goroutine,有的话,就不需创建了。
如cas操作成功,则继续调用startm创建一个新的或是唤醒一个处于睡眠状态的工作线程出来工作。
来看runtime/proc.go文件1947行代码分析startm:
// Schedules some M to run the p (creates an M if necessary).// If p==nil, tries to get an idle P, if no idle P's does nothing.// May run with m.p==nil, so write barriers are not allowed.// If spinning is set, the caller has incremented nmspinning and startm will// either decrement nmspinning or set m.spinning in the newly started M.//go:nowritebarrierrecfunc startm(_p_ *p, spinning bool) {

本文深入探讨Go语言中Goroutine的唤醒与创建过程,涉及如何将等待的Goroutine放入运行队列、何时创建新工作线程、以及线程的唤醒和创建细节。通过分析`ready`、`wakep`、`startm`和`newm`等函数,揭示Go运行时如何高效地管理线程,确保CPU资源充分利用。
最低0.47元/天 解锁文章
870

被折叠的 条评论
为什么被折叠?



