上卷 程序设计
中卷 标准库
- 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.3 执行
切换到 G.stack,执行并发函数。
// proc.go // 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. func execute(gp *g, inheritTime bool) { _g_ := getg() // 设置任务状态。 _g_.m.curg = gp gp.m = _g_.m casgstatus(gp, _Grunnable, _Grunning) gp.waitsince = 0 gp.preempt = false gp.stackguard0 = gp.stack.lo + _StackGuard // 累加执行计数器。 if !inheritTime { _g_.m.p.ptr().schedtick++ } // 执行。 gogo(&gp.sched) }
函数 gogo 是用汇编实现的执行函数,它会切换到 G.stack,并跳转到用户函数。详情参考下节。
结束
无论是 execute,还是 gogo 都没有直接回到 schedule 的意思。那么,如何清理执行现场?比如将 dead G 放回复用链表。如何继续循环调度呢?
在 newproc1 设置 G.sched 的时候,实际保存在 pc 的是 goexit。
// proc.go func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g { newg.sched.sp = sp newg.stktopsp = sp newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function newg.sched.g = guintptr(unsafe.Pointer(newg)) gostartcallfn(&newg.sched, fn) newg.gopc = callerpc newg.startpc = fn.fn return newg }
关键就在于 gostartcallfn 做了什么。
// stack.go // adjust Gobuf as if it executed a call to fn // and then stopped before the first instruction in fn. func gostartcallfn(gobuf *gobuf, fv *funcval) { var fn unsafe.Pointer if fv != nil { fn = unsafe.Pointer(fv.fn) } else { fn = unsafe.Pointer(abi.FuncPCABIInternal(nilfunc)) } gostartcall(gobuf, fn, unsafe.Pointer(fv)) }
// sys_x86.go // adjust Gobuf as if it executed a call to fn with context ctxt // and then stopped before the first instruction in fn. func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) { sp := buf.sp // 将 G.sched.sp 上移一个指针空间。 sp -= goarch.PtrSize // 存储 G.sched.pc,也就是预设的 goexit。 *(*uintptr)(unsafe.Pointer(sp)) = buf.pc // 调整 G.sched 设置,此时 pc 指向用户函数。 buf.sp = sp buf.pc = uintptr(fn) buf.ctxt = ctxt }
当 execute 通过 gogo 切换到 G.stack,执行用户函数时,其堆栈如下:
lo | | | G.fn | +----------+ G.sched.sp | goexit | hi +----------+ | |
而 gogo 以 JMP
调用 G.fn,就是说不会将 gogo 的 IP/PC 入栈。等 G.fn RET
执行时, POP PC
的结果自然是 goexit。该函数以 mcall 切换回 g0 栈,完成清理操作,重新回到调度循环。
// asm_amd64.s // The top-most function running on a goroutine // returns to goexit+PCQuantum. TEXT runtime·goexit(SB),NOSPLIT|TOPFRAME,$0-0 CALL runtime·goexit1(SB) // does not return
// proc.go // Finishes execution of the current goroutine. func goexit1() { mcall(goexit0) } // goexit continuation on g0. func goexit0(gp *g) { _g_ := getg() _p_ := _g_.m.p.ptr() // 清理已结束任务状态,并记入 GC。 casgstatus(gp, _Grunning, _Gdead) gcController.addScannableStack(_p_, -int64(gp.stack.hi-gp.stack.lo)) gp.m = nil gp.lockedm = 0 ... gp.timer = nil // 解除与当前 M 的关联。 dropg() // 放回闲置链表(P.gFree),等待复用。 gfput(_p_, gp) // 重回调度函数。 schedule() }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论