GMP模型
GMP模型解释
- G:Goroutine,可以理解为一个任务/工作包,保存协程执行上下文(栈,pc寄存器)
- M:OS 线程,真正的执行载体,G 最终需要M来执行
- P:调度者,协调M去运行G,G会存放在P的执行队列中
G 阻塞场景讨论
G 出现阻塞的两种情况:
**程序语意上(用户态)的等待:**channel接受等待,time.After,sync锁
- G 加入等待队列
- M不会阻塞,P绑定的M继续处理队列其他可执行的G
- G 等待条件满足(收到 channel 数据、定时器到期、锁释放),G 会被唤醒,重新放入 P 的可运行队列,等待调度
整个G的切换唤醒都存在于用户态,性能开销相对较少
**线程等待内核操作:**内核处理文件IO,网络IO等
- M 和 P 解绑,M进入内核挂起,卡在系统调用里面,无法执行go代码
- P 失去绑定的M,会寻找一个空闲M,接管P接续跑队列内剩下的G
- 内核阻塞完成后,M 尝试抢回原来的P。抢不到,G放入全局队列,M放线程池等待复用
GO 和 Java 对比
✅ Go 更占优场景
- 海量 IO 密集小任务:几十万并发连接、大量短生命周期任务(网关、反向代理、爬虫、消息消费)。Goroutine 内存占用更小,runtime 调度很成熟,生态原生就是协程优先。
- 简单并发编程:channel + select,原生语言内置,写并发代码心智负担低。
- 资源受限容器环境:大量协程场景内存占用更低。
✅ Java 更占优场景
- CPU 密集型重计算 Java JIT 长时间运行后极致优化,成熟的 GC(ZGC/Shenandoah)低延迟;Go 虽然也不错,但长时间复杂业务计算,JIT 经常跑出更好性能。
补充 GMP 核心要点
P 的数量默认等于
GOMAXPROCS(默认 CPU 核心数),代表同一时刻最多有多少个 M 并行执行 Go 代码M 的数量不固定:按需创建,内核阻塞时会新增 M,阻塞 M 会被缓存复用
G 数量可以非常大(几十万级别),栈初始很小,可动态伸缩
两种阻塞核心区别:
- 用户态阻塞:M 不放手 P,换 G 继续跑
- 内核 syscall 阻塞:M 放手 P,P 换个 M 继续跑