在 Go 语言中,何时会发生 Goroutine 的调度?请列出并解释主要的调度时机。
考察说明
考查对 Go 运行时调度器(GMP 模型)的理解,特别是触发 goroutine 切换的时机。
回答思路
- 【回答框架 1】Go 的调度器基于 GMP 模型,其中 M 代表系统线程,P 代表逻辑处理器,G 代表 goroutine。调度器负责在适当的时候将 G 从 P 的运行队列中取出并绑定到 M 上执行。
- 【回答框架 2】主要的调度时机包括主动调度:当 goroutine 执行阻塞操作(如 channel 发送/接收、锁等待、time.Sleep、系统调用)时,会主动让出处理器,使得其他 goroutine 获得执行机会。
- 【回答框架 3】被动调度:当 goroutine 发生抢占时,例如运行时间超过 10ms 或遇到抢占点(如函数调用),编译器会插入抢占检查,调度器会暂停当前 goroutine,切换为其他任务。
- 【回答框架 4】另外,在 GC 或 垃圾回收期间,会进行 stop the world 调度,等待所有 goroutine 达到安全点,再执行回收。
- 【关键点 1】调度时机分为主动调度(阻塞操作让出)和被动调度(抢占和 GC STW)。
- 【关键点 2】阻塞操作包括 channel、锁、time.Sleep、系统调用等。
- 【关键点 3】抢占主要通过 sysmon 检测超时并插入抢占。
- 【关键点 4】GC 也会触发 goroutine 暂停和恢复。
- 【易错点 1】不要误以为所有阻塞操作都会立即触发调度,例如无缓冲 channel 的发送和接收确实会阻塞并让出,但有缓冲 channel 在缓冲区未满时可能不会阻塞。
- 【易错点 2】避免混淆用户态调度与内核线程调度,goroutine 切换不会必然导致系统线程的上下文切换。
- 【易错点 3】不要遗漏抢占机制的存在,过去 Go 版本仅依赖协作式调度,但现代 Go 已有非协作式抢占。