在 Go 语言中,有哪些操作会导致 runtime 调度器发生调度?
考察说明
考查对 Go 运行时调度触发机制的理解,包括协作式调度与抢占式调度的触发条件。
回答思路
- 【回答框架 1】Go 的 runtime 调度器采用协作式与抢占式结合的方式,触发调度的操作主要分为主动让出、阻塞等待和抢占三类。主动让出包括调用 runtime.Gosched(),它会将当前 goroutine 放入可运行队列并让出 P,使其他 goroutine 获得执行机会。
- 【回答框架 2】阻塞操作会触发调度,例如 channel 发送或接收、sync.Mutex 加锁、time.Sleep、网络 I/O 等。当 goroutine 因这些操作阻塞时,调度器会将当前 goroutine 挂起,并调度其他可运行的 goroutine 到当前 P 上执行。
- 【回答框架 3】系统调用也会触发调度,当 goroutine 执行系统调用时,P 会被释放,调度器可能将 P 分配给其他 M 或执行其他 goroutine。此外,runtime 的监控线程 sysmon 会周期性地检查,对运行时间过长的 goroutine 发起抢占,这是 Go 1.14 引入的基于信号的异步抢占机制。
- 【回答框架 4】垃圾回收(GC)也会触发调度,GC 的标记和清扫阶段需要停止所有用户 goroutine(STW),或通过辅助标记机制让 goroutine 参与 GC,这些过程都会涉及调度器的协调。
- 【回答框架 5】此外,runtime 的定时器(timer)到期、runtime 的 finalizer 执行、以及 runtime 的 netpoller 检测到网络事件就绪时,也会唤醒对应的 goroutine 并触发调度。
- 【关键点 1】主动让出:runtime.Gosched() 显式让出 P。
- 【关键点 2】阻塞操作:channel、锁、sleep、网络 I/O 等导致 goroutine 阻塞。
- 【关键点 3】系统调用:进入系统调用时释放 P,可能触发调度。
- 【关键点 4】抢占:sysmon 监控,对运行超过 10ms 的 goroutine 发起异步抢占。
- 【关键点 5】GC 和定时器:GC 的 STW 和辅助标记、定时器到期、netpoller 事件都会触发调度。
- 【易错点 1】不要认为只有显式调用 Gosched 才会触发调度,阻塞和抢占是更常见的触发方式。
- 【易错点 2】不要混淆 goroutine 的阻塞与系统调用,系统调用会释放 P,而阻塞不一定释放 P。
- 【易错点 3】不要忽略 GC 和 sysmon 的抢占机制,它们是 runtime 调度的重要组成部分。