giftiaのblog

GMP 模型

· giftia

GMP 模型

img

  • G:Goroutine
  • M:工作线程(OS thread)也被称为 Machine。
  • P:Goroutine 调度器,可以通过 GOMAXPROCS 进行修改。

调度流程

  1. 创建 Goroutine
    • 当通过 go func() 创建新的 Goroutine 时,G 会首先被加入到与当前 P 关联的本地队列中。
    • 如果 P 的本地队列已满(超过 256 个 G),则新的 G 会被放入全局队列。
  2. 调度与执行
    • 每个 M 与一个 P 绑定,M 从 P 的本地队列中获取一个 G 来执行。
    • 如果 P 的本地队列为空,M 会尝试从全局队列或其他 P 的本地队列中偷取(work stealing)任务执行。
  3. 系统调用与阻塞
    • 当 G 执行过程中发生阻塞或系统调用,M 也会被阻塞。这时,P 会解绑当前的 M,并尝试寻找或创建新的 M 来继续执行其他 G。
    • 阻塞结束后,原来的 M 会尝试重新绑定一个 P 继续执行。

知识点

  • 一个 P 对应一个 M。(但P和M的数量不相关)
  • 每个 P 都有一个本地运行队列,里面存储着即将被执行的 G。
  • 全局队列有锁,本地队列没有锁。
  • G 会关联某个 P,如果没有 P,会新建一个 P。
  • G 优先放关联的 P 的本地队列,如果关联的 P 的本地队列满了,不会优先放到其他 P 的本地队列,而是会将本地队列中一半的 G 移动到全局队列,然后把新创建的 G 放入本地队列。
  • P 的本地队列 G 执行完后,会去全局队列获取新的 G。
  • P 的本地队列 G 执行完后,全局队列也没有了,就会 work-stealing。
  • 全局队列中 G 和一个新 G,新 G 会优先放入本地队列。(全局队列会有饥饿现象,但减少了锁竞争)
  • 每调度 61 次,M 就会尝试从全局队列中获取一个 G,即使本地队列还有任务。

work-stealing

P 的本地队列 G 执行完后,全局队列也没有了。就从隔壁的本地队列偷一半的 G。

G,P,M 的个数问题

  1. G(Goroutine)的个数
    • 理论上无限制。
    • 实际受内存限制。
  2. P(Processor)的个数
    • 默认等于逻辑 CPU 的核心数。
    • 由GOMAXPROCS决定。
  3. M(Machine/Thread)的个数
    • 动态创建和销毁。
    • M的最大数量默认通常是10000。
    • M与P之间没有绝对的固定关系。

为什么要有 P?

  • 每个 P 有自己的本地队列,大幅度的减轻了对全局队列的直接依赖,所带来的效果就是锁竞争的减少。而 GMP 模型的性能开销大头就是锁竞争。
  • 每个 P 相对的平衡上,在 GMP 模型中也实现了 Work Stealing (工作量窃取机制)算法,如果 P 的本地队列为空,则会从全局队列或其他 P 的本地队列中窃取可运行的 G 来运行,减少空转,提高了资源利用率。