Golang 的调度器可以描述为 GPM 模型,GPM 的含义如下:
- G 代表 groutine
- P 代表 logical processor,逻辑处理器,每个 G 需要运行都需要分配到一个 P
- M 代表 machine 是一个 OS Thread 的抽象,只有绑定了 M 的 P 才能真正的运行
GPM 模型的样子可以抽象为下面这张图:

抢占式调度
在 go1.14 之前,Golang 的调度器是基于协作的抢占式调度,其工作原理大概如下:
- 编译器会在调用函数前插入
runtime.morestack - Go 语言运行时会在垃圾回收暂停程序、系统监控发现 Goroutine 运行超过 10ms 时发出抢占请求
StackPreempt - 当发生函数调度时,可能会执行编译器插入的
runtime.morestack,它调用runtime.newstack会检查 Goroutine 的stackguard0字段是否为StackPreempt; - 如果
stackguard0是StackPreempt,就会触发抢占让出当前线程
在 go1.14 之后,Golang 实现了基于信号(SIGURG)的抢占式调度,其调度过程大概如下:
- 程序启动时,在
runtime.sighandler中注册SIGURG信号的处理函数runtime.doSigPreempt; - 在触发垃圾回收的栈扫描时会调用
runtime.suspendG挂起 Goroutine,该函数执行两个功能:- 将
_Grunning状态的 Goroutine 标记称可以被抢占,preemptStop设置为true; - 调用
runtime.preemptM触发抢占;
- 将
runtime.preemptM会调用runtime.signalM向线程发送程序SIGURG;- 操作系统会中断正在运行的线程并执行预先注册的信号处理函数,(1) 中注册的
runtime.doSigPreempt; runtime.doSigPreempt函数会处理抢占信号,获取当前的 SP 和 PC 寄存器并调用runtime.sigctxt.pushCall;runtime.sigctxt.pushCall会修改寄存器并在程序回到用户态时执行runtime.asyncPreempt;- 汇编
runtime.asyncPreempt会调用运行时函数runtime.asyncPreempt2; runtime.asyncPreempt2会调用runtime.preemptPark;runtime.preemptPark会修改当前 Goroutine 的状态到_Gpreempted并调用runtime.schedule让当前函数陷入休眠并让出线程,调度器会选择其它的 Goroutine 继续执行。
Reference
- Go 语言设计与实现
- 《Go 语言精进之路:从新手到高手的编程思想、方法和技巧 1》,32.2 goroutine 调度模型与演进过程。