[典藏版] Golang 调度器 GMP 原理与调度全分析

Go 调度本质是把大量 goroutine 分配到少量线程上去执行,并利用多核并行

如果线程想运行 goroutine必须先获取 P,P 中包含可运行的 G 队列

  1. 全局队列 Global Queue: 存放等待运行的 G
  2. P 本地队列:类似 GQ,存的数量有限 ≤ 256,新建 G' 优先加入到 P 本地队列,如果满了,则把本地队列中一半 G 移动到全局队列
  3. P 列表:所有 P 在程序启动时创建,保存在数组,最多 GOMAXPROCS 个
  4. M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去。

在 Go 中,线程是运行 goroutine 的实体,调度器的功能是把可运行的 goroutine 分配到工作线程上。Goroutine 调度器和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行

Untitled

M 与 P 的数量没有绝对关系,一个 M 阻塞,P 就会去创建或者切换另一个 M,所以,即使 P 的默认数量是 1,也有可能会创建很多个 M 出来。

调度器设计策略

线程复用:避免频繁创建、销毁线程

  1. work stealing 机制:当本线程无可运行 G,尝试从其他线程绑定的 P 偷 G,而不是销毁线程
  2. hand off 机制: 当本线程因为 G 进行系统调用阻塞时,线程释放绑定的 P,把 P 转移给其他空闲的线程执行

go func 调度