goroutine 切换的时候发生了什么?
Goroutine 怎么主动让出权力?
Goroutine 上下文切换的时候会发生什么?
g 切换的时候, 要做哪些事情?
goroutine 切换的时候发生了什么?
Goroutine 怎么主动让出权力?
golang.org/pkg/runtime
Goroutine 上下文切换的时候会发生什么?
跟进去, 看 Gosched 的源码:
- // Gosched yields the processor, allowing other goroutines to run. It does not
- // suspend the current goroutine, so execution resumes automatically.
- func Gosched() {mcall(gosched_m)
- }
- mcall:
- // mcall switches from the g to the g0 stack and invokes fn(g),
- // where g is the goroutine that made the call.
- // mcall saves g's current PC/SP in g->sched so that it can be restored later.
- // It is up to fn to arrange for that later execution, typically by recording
- // g in a data structure, causing something to call ready(g) later.
- // mcall returns to the original goroutine g later, when g has been rescheduled.
- // fn must not return at all; typically it ends by calling schedule, to let the m
- // run other goroutines.
- /$/ mcall can only be called from g stacks (not g0, not gsignal).
- /$/ This must NOT be go:noescape: if fn is a stack-allocated closure,
- // fn puts g on a run queue, and g executes before fn returns, the
- // closure will be invalidated while it is still executing.
- func mcall(fn func(*g))
发现 mcall 的作用是从 g 切到 g0, 然后执行 fn(g) 这篇文章 里说过, g0 是绑定在 m 上的一个 g, 使用系统栈
我们接下来跟 gosched_m:
- // Gosched continuation on g0.
- func gosched_m(gp *g) {
- if trace.enabled {
- traceGoSched()
- }
- goschedImpl(gp)
- }
然后是 goschedImpl(gp):
- func goschedImpl(gp *g) {
- status := readgstatus(gp)
- if status&^_Gscan != _Grunning {
- dumpgstatus(gp)
- throw("bad g status")
- }
- casgstatus(gp, _Grunning, _Grunnable)
- dropg()
- lock(&sched.lock)
- globrunqput(gp)
- unlock(&sched.lock)
- schedule()
- }
可以看到, 让出权力的过程是:
读取当前 g 的状态, 将状态从 _Grunning 切换成 _Grunnable
解除当前 g 和 m 的关系
锁定全局调度器
将这个 g 丢到全局 g 队列去
解锁全局调度器
调用 schedule 去寻找可执行的 g
关于 schedule 的分析, 看 这篇文章
g 切换的时候, 要做哪些事情?
如果你跟进了 schedule, 会发现, 找到了 g 之后, 会执行 execute 函数:
- // Schedules gp to run on the current M.
- // If inheritTime is true, gp inherits the remaining time in the
- // current time slice. Otherwise, it starts a new time slice.
- // Never returns.
- /$/ Write barriers are allowed because this is called immediately after
- // acquiring a P in several places.
- /$/go:yeswritebarrierrec
- func execute(gp *g, inheritTime bool) {
- _g_ := getg()
- casgstatus(gp, _Grunnable, _Grunning)
- gp.waitsince = 0
- gp.preempt = false
- gp.stackguard0 = gp.stack.lo + _StackGuard
- if !inheritTime {
- _g_.m.p.ptr().schedtick++
- }
- _g_.m.curg = gp
- gp.m = _g_.m
- // Check whether the profiler needs to be turned on or off.
- hz := sched.profilehz
- if _g_.m.profilehz != hz {
- setThreadCPUProfiler(hz)
- }
- if trace.enabled {
- // GoSysExit has to happen when we have a P, but before GoStart.
- // So we emit it here.
- if gp.syscallsp != 0 && gp.sysblocktraced {
- traceGoSysExit(gp.sysexitticks)
- }
- traceGoStart()
- }
- gogo(&gp.sched)
- }
然后继续跟进 gogo:
func gogo(buf *gobuf)
发现是汇编写的, 那我们搜索一下, 然后跳到 amd64 版本的:
- // void gogo(Gobuf*)
- // restore state from Gobuf; longjmp
- TEXT runtime.gogo(SB), NOSPLIT, $16-8
- MOVQ buf+0(FP), BX // gobuf
- MOVQ gobuf_g(BX), DX
- MOVQ 0(DX), CX // make sure g != nil
- get_tls(CX)
- MOVQ DX, g(CX)
- MOVQ gobuf_sp(BX), SP // restore SP
- MOVQ gobuf_ret(BX), AX
- MOVQ gobuf_ctxt(BX), DX
- MOVQ gobuf_bp(BX), BP
- MOVQ $0, gobuf_sp(BX) // clear to help garbage collector
- MOVQ $0, gobuf_ret(BX)
- MOVQ $0, gobuf_ctxt(BX)
- MOVQ $0, gobuf_bp(BX)
- MOVQ gobuf_pc(BX), BX
- JMP BX
即把对应的寄存器的值刷成要执行的 g 的值, 如 SP,PC 等可以看看 gobuf 是啥:
- type gobuf struct {
- // The offsets of sp, pc, and g are known to (hard-coded in) libmach.
- /$/ ctxt is unusual with respect to GC: it may be a
- // heap-allocated funcval, so GC needs to track it, but it
- // needs to be set and cleared from assembly, where it's
- // difficult to have write barriers. However, ctxt is really a
- // saved, live register, and we only ever exchange it between
- // the real register and the gobuf. Hence, we treat it as a
- // root during stack scanning, which means assembly that saves
- // and restores it doesn't need write barriers. It's still
- // typed as a pointer so that any other writes from Go get
- // write barriers.
- sp uintptr
- pc uintptr
- g guintptr
- ctxt unsafe.Pointer
- ret sys.Uintreg
- lr uintptr
- bp uintptr // for GOEXPERIMENT=framepointer
- }
其实就是 goroutine 切换的时候要换的东西
参考资料:
Go 的源码: github.com/golang/go
Go 的自定义汇编: golang.org/doc/asm
来源: https://juejin.im/entry/5abda2846fb9a028dc411d1c