Go 是如何做抢占式调度的?

一、什么是"抢占式调度"?

先理解什么是"抢占"。

非抢占式(早期 Go)

早期 Go(1.14 之前):

  • goroutine 只有在

    • 函数调用
    • channel 操作
    • syscall
    • runtime 检查点

才会被切换。

如果一个 goroutine 写成:

go 复制代码
for {
}

会发生什么?

👉 它永远不会让出 CPU

👉 其他 goroutine 全部饿死

这叫:

协作式调度(主动让出)


抢占式调度

抢占式调度是:

即使 goroutine 不愿意让出 CPU,调度器也可以强制打断它

这就是现代 Go(1.14+)的机制。


二、Go 现在是如何"强制打断"的?

核心答案:

通过"信号 + 栈检查"实现异步抢占

我们拆开讲。


三、抢占的触发条件

Go 会给每个运行中的 goroutine 设置一个"时间片"。

大概:

复制代码
10ms 左右

如果:

某个 G 连续运行超过时间片

调度器就会尝试抢占它。


四、真正的技术实现(关键来了)

Go 通过:

复制代码
发送操作系统信号(SIGURG)

去打断正在运行的线程 M。

步骤如下:


第一步:标记需要抢占

调度器发现:

这个 G 运行太久了

就会:

go 复制代码
g.preempt = true

标记它需要被抢占。


第二步:给 M 发信号

Go runtime 会向正在运行的 M 发送一个系统信号:

复制代码
SIGURG

这会打断线程执行。


第三步:信号处理函数触发

当 M 收到信号时:

会执行 Go runtime 的信号处理函数。

这个函数会:

  1. 保存当前寄存器状态
  2. 检查是否可以安全抢占
  3. 修改程序计数器
  4. 跳转到调度函数

五、什么叫"安全抢占点"?

Go 不能在任何地方强行打断。

比如:

  • 正在修改栈
  • 正在执行 runtime 内部关键代码
  • 正在 GC 关键阶段

所以必须在"安全点"抢占。

Go 在函数调用前后插入:

复制代码
栈边界检查

类似:

asm 复制代码
CMP SP, stackGuard

如果发现:

  • 栈溢出
  • 或被标记需要抢占

就跳到 runtime。

这就实现了:

在几乎所有函数调用处都可被抢占


六、为什么 1.14 是重大升级?

1.14 之前:

  • 只能在函数调用点抢占
  • 死循环没函数调用就无法抢占

1.14 之后:

  • 加入"异步抢占"
  • 即使死循环也可以被打断

例如:

go 复制代码
for {
}

现在也会被强制暂停。


七、完整抢占流程(一步一步)

假设:

复制代码
G1 正在运行

1️⃣ G1 运行超过 10ms

2️⃣ 调度器标记 G1 需要抢占

3️⃣ 给 M 发送信号

4️⃣ M 收到信号

5️⃣ 保存当前执行现场

6️⃣ 切换到 runtime 调度代码

7️⃣ G1 状态改为 runnable

8️⃣ 放回队列

9️⃣ 执行其他 G


八、和操作系统抢占有什么区别?

操作系统线程调度:

  • 由内核控制
  • 时间片强制切换线程

Go goroutine 调度:

  • 用户态调度
  • 通过信号模拟"线程级抢占"
  • 更轻量

九、为什么这样设计?

目标是:

  • 防止 goroutine 独占 CPU
  • 保证公平性
  • 提高多核利用率
  • 让 GC 可以安全暂停世界

尤其是 GC:

如果不能抢占 goroutine:

复制代码
STW(Stop The World)会卡死

十、和 GC 的关系

Go 的 GC 需要:

在安全点暂停所有 goroutine

抢占机制保证:

  • 即使 goroutine 死循环
  • GC 也能暂停它

这非常关键。


十一、核心总结

一句话版本:

Go 通过向线程发送信号,在安全点打断 goroutine,实现异步抢占式调度。

再浓缩:

复制代码
时间片到
→ 标记抢占
→ 发信号
→ 跳到调度器
→ 切换 goroutine

十二、一个形象比喻

想象:

  • G 是学生
  • M 是老师
  • P 是教室

以前:

学生必须自己举手说"我讲完了"。

现在:

老师可以直接打断学生:

"时间到了,换人!"

这就是抢占式调度。

相关推荐
badhope16 小时前
Mobile-Skills:移动端技能可视化的创新实践
开发语言·人工智能·git·智能手机·github
码云数智-园园17 小时前
微服务架构下的分布式事务:在一致性与可用性之间寻找平衡
开发语言
C++ 老炮儿的技术栈17 小时前
volatile使用场景
linux·服务器·c语言·开发语言·c++
hz_zhangrl17 小时前
CCF-GESP 等级考试 2026年3月认证C++一级真题解析
开发语言·c++·gesp·gesp2026年3月·gespc++一级
大阿明17 小时前
Spring Boot(快速上手)
java·spring boot·后端
Liu6288818 小时前
C++中的工厂模式高级应用
开发语言·c++·算法
IT猿手18 小时前
基于控制障碍函数的多无人机编队动态避障控制方法研究,MATLAB代码
开发语言·matlab·无人机·openclaw·多无人机动态避障路径规划·无人机编队
AI科技星18 小时前
全尺度角速度统一:基于 v ≡ c 的纯推导与验证
c语言·开发语言·人工智能·opencv·算法·机器学习·数据挖掘
sunwenjian88618 小时前
Java进阶——IO 流
java·开发语言·python
参.商.18 小时前
【Day41】143. 重排链表
leetcode·golang