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

以前:

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

现在:

老师可以直接打断学生:

"时间到了,换人!"

这就是抢占式调度。

相关推荐
红尘散仙4 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记6 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
isyangli_blog6 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008116 小时前
FastAPI APIRouter
开发语言·python
Benszen6 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆6 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木6 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
喵个咪6 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
杨充7 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~7 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言