GMP 调度模型
Q: GMP 模型概述
GMP 是 Go 运行时调度器的核心模型:
| 角色 | 说明 |
|---|---|
| G (Goroutine) | 协程,包含栈、程序计数器、状态等信息 |
| M (Machine) | 操作系统线程,真正执行代码的载体 |
| P (Processor) | 逻辑处理器,持有本地 Goroutine 队列 |
全局队列 本地队列
[G][G][G] → P[G][G][G] → M(OS线程)
P[G][G][G] → M(OS线程)调度逻辑:
- M线程要运行任务就会先和P进行关联,从P的本地队列中获取G
- 如果队列中没有可用的G,就会尝试从全局队列中放一批到P的本地队列(需要加锁)
- 如果全局队列中也没有,就会从其他队列中获取G放到自己的本地队列里面
- 拿到可以运行的G之后,就会执行,然后重复这个过程
Q: 为什么引入 P?
核心目的:减少锁竞争,提升调度效率
去掉P的问题:所有M都需要从全局队列中获取goroutine,这就需要全局锁保护。在高并发场景下,大量M争抢同一把锁会造成严重的锁竞争,CPU大部分时间都浪费在等锁上,调度效率急剧下降
P的优势:P的存在实现了无锁的本地调度。每个P维护独立的本地队列,M绑定P后可以直接从本地队列取G执行,大部分情况下都不需要全局锁。只有本地队列空了才去偷取,这大大减少了锁竞争
工作窃取机制:
- 每个 P 持有本地 G 队列,减少锁竞争
- 当某个 P 的队列为空时,可以从其他 P 的队列尾部窃取 G
- P 的数量由
GOMAXPROCS控制,默认为 CPU 核心数
Q: 能不能去掉P层?
不能,去掉P会带来严重的性能问题:
锁竞争加剧:去掉P之后,所有M都需要从全局队列中获取goroutine,这就需要全局锁保护。在高并发场景下,大量M争抢同一把锁会造成严重的锁竞争,CPU大部分时间都浪费在等锁上
P实现无锁调度:P的存在实现了无锁的本地调度。每个P维护独立的本地队列,M绑定P后可以直接从本地队列取G执行,大部分情况下都不需要全局锁。只有本地队列空了才去偷取,这大大减少了锁竞争
Q: Goroutine 的调度时机?
- 函数调用(编译器插入调度检查点)
- 系统调用(M 与 P 解绑,等待 IO)
runtime.Gosched()主动让出- 通道操作阻塞
time.Sleep- GC STW
Q: 调度阻塞的处理机制?
用户态阻塞
如果G因为channel发生阻塞,这个时候M不会被阻塞:
- 只需要把当前阻塞的G放到等待队列(比如channel的等待队列)
- 切换到下一个G进行执行
- 阻塞的G被唤醒后,会尝试加入唤醒者所在P的runnext队列,如果满了就加入本地队列,最后是全局队列
系统调用阻塞
发生系统调用阻塞时M会进入内核态,所以会阻塞M:
- 需要把P和M断开,去连接一个新的M(如果没有就新建)
- 继续执行别的G
- 系统调用完成后,G会尝试加入到一个活跃P的本地队列,如果没有就会被加入到全局队列中
Q: Go Scheduler(调度器) 的工作原理?
基本概念:Go scheduler是Go的协程调度器,它的主要工作是决定哪个goroutine在哪个线程上运行,以及何时进行上下文切换。scheduler的核心是schedule()函数,它在无限循环中寻找可运行的goroutine。当找到后通过execute()函数切换到goroutine执行,goroutine主动让出或被抢占时再回到调度循环。
调度策略:
- Go 1.14之前:协作式调度,只有函数调用时才会检查preempt标志,如果是true就把自己挂起。缺点是如果G里面没有函数调用,执行大循环时会导致其他G没有机会执行
- Go 1.14之后:异步抢占机制,检测到运行了10ms以上的G,向运行G的M发送信号,触发专门的gsignal goroutine,在信号处理过程中设置preempt=true,即使G没有函数调用也会被强制挂起
Q: m0 和 g0 是什么?
m0
m0是go启动时创建的主线程,主要用来:
- 执行Go程序的启动
- 调度器初始化
- 内存管理器初始化
- 垃圾回收器初始化
- 创建g0然后执行main函数
- 之后其实就和其他M线程一样了
g0
g0是调度协程,主要负责调度逻辑:
- 职责:goroutine创建、销毁、调度。比如在g1协程上执行,需要调度时(goroutine阻塞、抢占、系统调度等),M就会切换到g0上执行调度逻辑,选出下一个运行的goroutine,然后运行
- 为什么需要g0:
- 调度器执行时会操作全部goroutine,如果和普通goroutine共用可能会相互干扰
- 如果在普通goroutine栈上运行,如果调度的下一个goroutine还是自己,可能出现递归现象导致栈溢出
- g0提供一个安全的栈来执行调度