跳到主要内容

GMP 模型-怎么确定分配给哪个 P

我们通过下面这个问题来理解 GMP 这个模型:

我们看 GMP 模型可以知道有多个 P 同时在运行,那么是怎么决定放在哪个 P 队列上面的?

确实,如果仅仅是简单地把所有新 G 放到当前 P,会造成严重的负载不均。Go 调度器通过以下机制解决这个问题:

工作窃取(Work Stealing)机制

func main() {
// 假设在 P0 上执行
for i := 0; i < 1000; i++ {
go worker(i) // 这些 G 初始都放在 P0
}
}

调度器的应对策略:

  • P0 本地队列满了(256个)→ 后续的 G 放入全局队列
  • 空闲的 P1、P2、P3 会:
    • 从 P0 的本地队列尾部窃取一半的 G
    • 全局队列获取 G

可以参考下面实际的负载均衡过程:

初始状态:
P0: [G1,G2,G3...G256] (满)
P1: []
P2: []
P3: []
全局队列: [G257,G258,G259...]

工作窃取后:
P0: [G1,G2...G128]
P1: [G129,G130...G256] (从P0窃取)
P2: [G257,G258...] (从全局队列获取)
P3: [G270,G271...] (从全局队列获取)

全局队列的分发策略

调度器会定期(每61次调度)从全局队列获取 G,确保:

  • 不会饿死全局队列中的 G
  • 实现更均匀的分布

可以实际测试验证

func main() {
runtime.GOMAXPROCS(4)

for i := 0; i < 1000; i++ {
go func(id int) {
fmt.Printf("G%d running on P%d\n", id, getCurrentP())
time.Sleep(time.Millisecond)
}(i)
}

time.Sleep(time.Second)
}

运行结果会显示 G 分布在不同的 P 上执行。

总结: 虽然新 G 初始都放在当前 P,但通过工作窃取全局队列分发动态调度等机制,最终实现了负载均衡。这是一个"局部不均 → 全局均衡"的过程。