Go中GMP调度模型是如何优雅地应对G阻塞?

在这里插入图片描述

前言

「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 调度中最巧妙的一环!

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 池。

🔄 五、阻塞/唤醒过程图示

G1阻塞中(如读channel)
┌─────────────┐
│ M1执行G1    │
└────┬────────┘
     ↓
[G1 阻塞,进入等待队列] ──┐
                         ↓
           M1 脱钩,空出 P1
                         ↓
              P1 调度其他 G(如G2)
                         ↓
           G1 等待唤醒事件(如chan有值)
                         ↓
         [G1 变为runnable,重新入队]
                         ↓
           P1 再次调度 G1(或其他P抢占)

✅ 六、总结流程核心词汇(调度器关键词)

关键词含义
waitingG 在等待资源(如channel)
syscallG 或 M 正在系统调用
runnableG 可以调度执行
in-syscallM 被系统调用阻塞中
handoffM 把 P 让给另一个 M
netpoller异步网络I/O事件检测器

🎯 七、总结一句话

Go 的 GMP 调度器通过 挂起阻塞 G,解绑其占用的 M 和 P,调度其他 G 替代执行,在需要时再恢复原 G,从而优雅实现了非阻塞、高并发、极致资源利用的调度体系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卜锦元

白嫖是人类的终极快乐,我也是

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值