
前言
「GMP 是如何优雅地应对 G 阻塞」,这是 Go 调度器最引以为傲的设计之一
。我们接下来详细讲解 Go 调度器在 G阻塞时的完整流程逻辑,从不同场景触发、到资源切换、再到恢复调度,全流程解剖。
🧠 为什么要优雅处理 G 的阻塞?
如果一个 Goroutine(G)阻塞了,而它仍占据 M(线程)和 P(调度器)资源,那将:
浪费系统线程
阻碍更多 goroutine 被调度
降低系统并发度和吞吐量
所以 Go 设计了一套 "快速摘下阻塞 G,调度其它 G" 的机制,以保持系统流畅运行。
🧰 一、G 阻塞场景的分类
Go 通过不同的策略分别处理以下几类阻塞场景:
阻塞类型 | 场景举例 | M 是否阻塞? | 特殊机制 |
---|---|---|---|
用户态阻塞 | channel、select、mutex | ❌ 不阻塞 | G 移除、M+P 继续调度 |
系统调用阻塞 | net.Dial、文件I/O等 | ✅ 会阻塞 | M 进 syscall,启动新 M |
时间阻塞 | time.Sleep、定时器 | ❌ 不阻塞 | 放入时间队列,按时唤醒 |
手动挂起 | Gosched、yield | ❌ 不阻塞 | G 主动让出调度权 |
🔁 二、调度器的核心目标
-
不让 G 的阻塞影响 M 和 P 的继续执行
-
能快速调度其他 G 保证运行不中断
-
阻塞 G 一旦就绪,能快速恢复调度
⚙️ 三、详细流程逻辑(展开讲解)
我们拆解为两个流程:
🧩 A. 阻塞发生时的处理流程
假设当前 M1
正在运行 G1
,此时 G1
阻塞了:
🔹【步骤1】检测到阻塞点(用户态 or 系统调用)
例如:
-
ch <- val
阻塞 -
net.Dial()
阻塞 -
mutex.Lock()
等待
🔹【步骤2】调度器标记状态
-
G1
被标记为waiting
/syscall
等状态 -
放入对应的等待队列(channel queue、等待互斥锁、netpoller 等)
🔹【步骤3】解绑 M 和 G
-
将
G1
从 M 和 P 脱钩 -
M 和 P 空出可以继续运行其它 G
🔹【步骤4】P 获取新 G
-
从本地队列、全局队列或 work stealing 获取新的 G
-
如果本地无 G,调度器考虑休眠 M,或尝试 global steal
🔹【步骤5】如果 syscall 阻塞 M
-
M 会进入
in-syscall
状态 -
调度器创建或复用一个新的 M(如果当前活跃 M 数 < GOMAXPROCS)
-
保证此 P 始终有 M 可用,不影响调度
🧩 B. 阻塞结束时的恢复流程
当之前阻塞的 G(如 G1)满足条件,恢复执行:
🔹【步骤1】G 状态改为 runnable
例如:
-
channel 有值
-
mutex 解锁
-
epoll/kqueue 返回可读/写事件
-
sleep 时间到
🔹【步骤2】唤醒 G
-
G1 被放入对应的 P 的本地队列(如果还绑定)
-
如果没有活跃 M 与 P 绑定,会唤醒空闲的 M 来绑定并执行 G
🔹【步骤3】G 被调度执行
- 通过正常调度流转,G1 会重新被某个 M 拿到继续执行
📜 四、系统调用阻塞的特殊处理逻辑(关键)
这是 GMP 调度中最巧妙的一环!
go
func schedule() {
if m.syscall {
// 当前 M 正在 syscall(可能会阻塞)
handoff P 给新 M
mark m in-syscall
start new M to keep P busy
}
}
一旦 M 进入 syscall 且检测到潜在阻塞,P 会被剥离给新 M,保证系统调度继续,旧的 M 自己去系统调用,回来了再加入空闲 M 池。
🔄 五、阻塞/唤醒过程图示
go
G1阻塞中(如读channel)
┌─────────────┐
│ M1执行G1 │
└────┬────────┘
↓
[G1 阻塞,进入等待队列] ──┐
↓
M1 脱钩,空出 P1
↓
P1 调度其他 G(如G2)
↓
G1 等待唤醒事件(如chan有值)
↓
[G1 变为runnable,重新入队]
↓
P1 再次调度 G1(或其他P抢占)
✅ 六、总结流程核心词汇(调度器关键词)
关键词 | 含义 |
---|---|
waiting |
G 在等待资源(如channel) |
syscall |
G 或 M 正在系统调用 |
runnable |
G 可以调度执行 |
in-syscall |
M 被系统调用阻塞中 |
handoff |
M 把 P 让给另一个 M |
netpoller |
异步网络I/O事件检测器 |
🎯 七、总结一句话
Go 的 GMP 调度器通过 挂起阻塞 G,解绑其占用的 M 和 P,调度其他 G 替代执行 ,在需要时再恢复原 G,从而优雅实现了非阻塞、高并发、极致资源利用的调度体系。