主动调度

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+,无需手动调用它。

gosched

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调试自定义函数

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的栈

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放入全局队列,而不是本地队列