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 是教室

以前:

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

现在:

老师可以直接打断学生:

"时间到了,换人!"

这就是抢占式调度。

相关推荐
1001101_QIA13 小时前
.exe does not seem to be a Qt executable.
开发语言·qt
小蒜学长13 小时前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
echome88813 小时前
Python 异常处理:try、else、finally 的正确搭配
开发语言·前端·python·异常处理·编程技巧
云和数据.ChenGuang13 小时前
Python 单继承与多继承深度剖析:从 MRO 到实战取舍
开发语言·人工智能·python·langchain
陈随易13 小时前
下载神器!Motrix 停更三年后重写回归
前端·后端·程序员
每天一道题13 小时前
CAP、Raft 与多数派:分布式系统如何达成一致?
分布式·后端·微服务
前端的日常14 小时前
一个人+AI做情侣食谱小程序,0 费用的服务器
前端·后端
小咕聊编程14 小时前
基于Python的豆瓣电影数据可视化系统构建分析
开发语言·python·信息可视化
IT_陈寒14 小时前
Vue的v-for里用了个临时变量,页面直接挂了
前端·人工智能·后端
ServBay15 小时前
本地模型已经跑起来了,Copilot Fusion 为什么还要联网?
后端·ai编程·github copilot