上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
4.5.1 调度
当 MP 启动后,开始执行任务调度,这是一个由四个函数组成的循环。
- schedule : 核心函数。检查相关状态(比如 GC),查找 runnable G。
- execute : 执行函数。通过汇编函数 gogo 切换到 G.stack,执行用户函数。
- G.fn : 用户函数。由
go func
传递而来。 - goexit : 退出函数。清理执行现场,重新进入 scheule 循环。
除 G.fn 外,调度循环在 g0 栈执行,不会向普通函数调用堆栈那样回溯。
通过提前设置 IP/PC,然后 jmp 的方式跳转。
+----------------+ +---------+ --- mstart --- M.g0 ---> | schedule | ----------- M.g0 ----------> | execute | +----------------+ +---------+ | globrunqget | | gogo | +----------------+ +---------+ | runqget | | +----------------+ | | findrunnable | | +----------------+ | ^ G.stack | | | | M.g0 | | | | v +-----------+ +------+ | goexit1 | <------------- M.g0 --------------- | G.fn | +-----------+ +------+ ........ mexit ... | goexit0 | +-----------+
// proc.go // One round of scheduler: find a runnable goroutine and execute it. // Never returns. func schedule() { // 检查当前 M 是否被锁定到特定 G。 if _g_.m.lockedg != 0 { stoplockedm() execute(_g_.m.lockedg.ptr(), false) // Never returns. } top: // 检查 STW !!! if sched.gcwaiting != 0 { gcstopm() goto top } // 垃圾回收启动标记。 if gp == nil && gcBlackenEnabled != 0 { gp = gcController.findRunnableGCWorker(_g_.m.p.ptr()) } // 每隔 60 次(任务执行计数器),执行一次全局队列任务,确保公平。 if gp == nil { if _g_.m.p.ptr().schedtick % 61 == 0 && sched.runqsize > 0 { gp = globrunqget(_g_.m.p.ptr(), 1) } } // 查找本地任务。 if gp == nil { gp, inheritTime = runqget(_g_.m.p.ptr()) } // 从不同途径获取任务。 // 如没有找到,则阻塞。 if gp == nil { gp, inheritTime = findrunnable() // blocks until work is available } // 找到,解除自旋状态。 if _g_.m.spinning { resetspinning() } // 检查 G 是否被绑定到特定 M。 if gp.lockedm != 0 { startlockedm(gp) goto top } // 执行任务。 execute(gp, inheritTime) }
如返回的是 P.runnext,那么
inheritTime = true
,执行计数器(P.schedtick)不会增加。这表示继承上个任务时间片,减少参与全局队列任务的次数。
另一方面,sysmon 通过比对两个不同时段的计数来判断 P 是否运行了新任务。
如没有,则检查执行时间是否过长(forcePreemptNS = 10ms),从而决定是否抢占调度。
从这点上说,runnext 继承时间片,延长执行时间,却没有增加计数,会提高抢占调度几率。
在
GODEBUG=scheddetail
输出里可以看到计数器信息。
单个 G 未必在一次调度内执行结束。因某些原因中断,被重新放回任务队列,等待断点恢复。
MP 切换任务前,会将执行状态(PC、SP)保存到 G.sched,其他寄存器数据保存到 G.stack。
切换本身(G100->G0->G101)消耗并不大,关键在于如何查找下一个 G 任务。
- 阻塞:block,syscall、mutex、channel。
- 抢占:preempt,sysmon。
locked
某些情况(syscall、cgo)下,G 会锁定 M。
一旦锁定,M 会休眠直到 G 出现,并执行。
同理,G 只能在锁定 M 上执行。
这么做是为了维持 M 的某些特殊状态(比如 signal 等)。
// LockOSThread wires the calling goroutine to its current operating system thread. // The calling goroutine will always execute in that thread, and no other goroutine will execute in it, // until the calling goroutine has made as many calls to UnlockOSThread as to LockOSThread. // If the calling goroutine exits without unlocking the thread, the thread will be terminated. func LockOSThread() { dolockOSThread() }
func dolockOSThread() { _g_ := getg() _g_.m.lockedg.set(_g_) _g_.lockedm.set(_g_.m) }
如当前 M 被某个 G 锁定,那么会将 P 交给其他 M,自己休眠等待女盆友出现。
为什么不自己找?而是休眠?
女盆友不知道在哪个队列,且按照队列顺序不知道什么时候轮到她。
如此,还不如休眠,等她自己出现。
// Stops execution of the current m that is locked to a g until the g is runnable again. // Returns with acquired P. func stoplockedm() { _g_ := getg() // 交出当前 P,让其他 M 匹配。 if _g_.m.p != 0 { _p_ := releasep() handoffp(_p_) } // 等待目标 G 出现。 notesleep(&_g_.m.park) noteclear(&_g_.m.park) // 绑定 P,回到 schedule,使用 execute 执行。 acquirep(_g_.m.nextp.ptr()) _g_.m.nextp = 0 }
如 M 遇到别人女盆友,那么将 P 让给其男友,自己去休眠。
// Schedules the locked m to run the locked gp. func startlockedm(gp *g) { // 该 G 的男盆友 M。 mp := gp.lockedm.ptr() // 当前 M 将 P 移交给其男友,并唤醒。 _p_ := releasep() mp.nextp.set(_p_) notewakeup(&mp.park) // 当前 M 休眠。 stopm() }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论