Skip to main content

Go: 抢占式调度

📅 2026-03-19 ✏️ 2026-03-21 Inside Go CS GO
No related notes

1 · Go: 抢占式调度#

runtime 为了避免 goroutine 长时间独占 CPU,会在 safe point 把它暂停并交回调度器。

1.1 · 协作式

goroutine自己让出 CPU。

常见时机:

  • 主动调用 runtime.Gosched()
  • I/O、syscall、channel、锁阻塞
  • 进入 runtime 明确安排的调度点

问题:纯计算或长循环如果不主动让,别的 goroutine 可能饿死。

1.2 · 抢占式:同步

runtime 决定“该让了”,但要等 goroutine 到下一个函数序言的栈检查点。

做法:复用函数序言里的栈扩容检查。runtime 把 stackguard0 设为 stackPreempt,让 goroutine 在下一次栈检查时进入 morestack/newstack,再切回调度器。

  • stackguard0 设为 stackPreempt
  • 等 goroutine 进入函数序言的栈检查
  • morestack/newstack 路径切回调度器

本质:runtime 要它让,不是 goroutine 自愿让。

1.3 · 抢占式:异步

Go 1.14 引入,解决 tight loop 长时间不到函数调用点的问题。

做法:

  • runtime 给承载该 goroutine 的线程(M)发信号
  • 信号处理器检查当前位置是不是 async safe point
  • 如果安全,转入 asyncPreempt 保存现场,再走抢占路径切回调度器

本质:比同步抢占更主动,但也不是任意位置都能打断。

1.4 · 安全点 safe point#

抢占不能发生在任意指令位置,只能发生在 safe point。

原因:runtime 要保证栈、寄存器、GC 状态可安全处理。

1.5 · 对比

  • 协作式:goroutine 自己让
  • 同步抢占:runtime 要它让,等到同步 safe point
  • 异步抢占:runtime 发线程信号,尽量在 async safe point 停下来

可以使用 GODEBUG=asyncpreemptoff=1:关闭异步抢占

2 · links#

  1. https://unskilled.blog/posts/preemption-in-go-an-introduction/
  2. https://github.com/gopherchina/conference/blob/master/2021/2.2.3%20Go%E8%AF%AD%E8%A8%80%E7%9A%84%E6%8A%A2%E5%8D%A0%E5%BC%8F%E8%B0%83%E5%BA%A6.pdf
  3. https://hidetatz.github.io/goroutine_preemption/
  4. https://tip.golang.org/doc/go1.14#runtime 1.14引入
  5. https://go.googlesource.com/proposal/+/master/design/24543-non-cooperative-preemption.md