goroutine主动调度 #
探寻runtime.Gosched() #
goroutine主动调度就是调用runtime.Gosched()
函数, 此函数会主动放弃与M的关联,并把自身(G)放入全局空闲G队列.当前的goroutine并不会退出,它进入全局G队列后可能会再次被调度运行.
src/runtime/proc.go:267
// Gosched yields the processor, allowing other goroutines to run. It does not
// suspend the current goroutine, so execution resumes automatically.
func Gosched() {
checkTimeouts()
mcall(gosched_m)
}
在go1.14前,编译器不会插入抢占点,用户代码可能会陷入CPU计算循环(即使代码中有函数调用),它可能会一直霸占CPU,这时候可能需要runtime.Gosched, 在go1.14,编译器会在函数前加入自动抢占点.所以对于go1.14+,无需手动调用它。
gdb主动调度 #
main.go
package main
import (
"runtime"
"sync/atomic"
)
var existFlag int32 = 2
// the function's body is empty
func addAssemble(x, y int64) int64
func add(a int64){
addAssemble(a, a)
atomic.AddInt32(&existFlag, -1)
}
func main() {
runtime.GOMAXPROCS(1)
go add(1)
go add(2)
for {
if atomic.LoadInt32(&existFlag) <=0{
break
}
runtime.Gosched()
}
}
add_amd.s
TEXT ·addAssemble(SB),$0-24
MOVQ x+0(FP), BX
MOVQ y+8(FP), BP
ADDQ BP, BX
MOVQ BX, ret+16(FP)
RET
编译程序
编译一下源代码:
go build -gcflags "-N -l" -o test .
准备mcall函数断点的文件
- gdb
list /usr/lib/golang/src/runtime/proc.go:267
- gdb
gdb调试自定义函数
define zxc
info threads
info register rbp rsp pc
step
continue
end
gdb过程 #
gdb总结 #
- mcall
- 把主动放弃调度的goroutine的寄存器值保存到sched(P中的成员变量?)的sp,bp,pc
- 也包括goroutine对应的G结构体的指针
- 把g0的sched.SP恢复到寄存器SP
- ⚠️这里没有pc,我们前面已经很详细的讨论过这个 TODO 加上跳转链接
- 根据执行传入实参(这里是
gosched_m
函数指针)- ⚠️从这里开始就是使用的g0的栈
- 把主动放弃调度的goroutine的寄存器值保存到sched(P中的成员变量?)的sp,bp,pc
gosched_m函数 #
// Gosched continuation on g0.
func gosched_m(gp *g) {
if trace.enabled {
traceGoSched()
}
goschedImpl(gp)
}
goschedImpl函数 #
func goschedImpl(gp *g) {
status := readgstatus(gp)
if status&^_Gscan != _Grunning {
dumpgstatus(gp)
throw("bad g status")
}
casgstatus(gp, _Grunning, _Grunnable)//改成可运行,而不是运行中的状态了
dropg() // m.curg = nil, gp.m = nil互相不关联
lock(&sched.lock) //因为要操作全局队列先加锁
globrunqput(gp)
unlock(&sched.lock) //unlock
schedule() //进入调度
}
这里要注意的一点就是是把这个Goroutine放入全局队列,而不是本地队列