Appearance
goroutine 调度器到底在做什么
goroutine 调度器的核心任务只有一句话:把大量用户态协程,尽量低成本地分配到有限的线程和 CPU 上执行,同时在阻塞、唤醒和抢占之间维持整体吞吐。
先说结论
- 调度器解决的是“海量协程如何高效运行”,不是“并发逻辑如何自动正确”
- 重点要看
G、M、P三者分工,而不是只记一个 GMP 名词 - 真正影响性能的,通常是阻塞点、抢占时机和任务分布是否均衡
核心边界
G、M、P 各自负责什么
G是 goroutine,本质上是待执行任务M是内核线程,真正占用系统线程资源P是调度上下文,负责本地运行队列、缓存和执行资格
可以把它理解成:任务很多,工人有限,工位数量也有限。调度器要做的是让工人尽量别闲着,也尽量别因为一个慢任务把全局拖住。
调度器为什么比线程模型更轻
goroutine 初始栈很小,切换主要发生在用户态,成本比线程切换低很多。所以 Go 才能比较自然地支持几十万级别的并发任务。
但这不等于 goroutine 没成本。协程数过多、频繁创建销毁、长时间阻塞 syscall,一样会把调度器拖慢。
哪些场景最容易影响调度效果
- 大量 goroutine 被系统调用阻塞
- 单个 goroutine 长时间占用 CPU,导致其他任务得不到运行机会
- 任务分布不均,本地队列很忙而其他 P 空闲
- GC、网络轮询、定时器回调和业务协程同时争用执行机会
实战里怎么判断
先分清是 CPU 忙还是阻塞多
如果系统 CPU 很高,先看是否存在热点循环、忙等、锁竞争;如果 CPU 不高但延迟上升,优先看网络阻塞、磁盘 IO、数据库调用和 channel 堵塞。
再看调度器有没有失衡
线上排查时,常见信号是 goroutine 数暴涨、请求 RT 抖动、PProf 里大量时间耗在调度或等待上。此时重点不是继续加 goroutine,而是减少无效并发和阻塞传播。
最后看并发模型有没有选错
不是所有场景都适合“一请求一堆 goroutine”。如果任务强依赖共享状态、外部 IO 慢、上游限流严格,盲目加协程只会放大抖动。
常见误区
goroutine 很轻,所以可以无限开
轻量不等于零成本。每个 goroutine 都会占栈、调度元数据和上下文切换成本。
GOMAXPROCS 调大就一定更快
它只决定可并行执行的 P 数量,不会自动解决锁竞争、IO 阻塞和业务热点。
调度器能替你兜底并发设计问题
调度器只负责“怎么跑”,不负责“跑得对不对”。channel、锁、context 和超时控制依然要自己设计好。
一句话总结
理解 goroutine 调度器,关键不是背 GMP,而是知道任务如何排队、线程如何承载、阻塞后如何恢复,以及什么情况下调度本身会成为性能瓶颈。