Go 调度程序的工作原理
Go 调度程序的工作原理
Go 调度程序负责在多个操作系统线程上高效地调度和执行 goroutine。以下是其工作原理的关键点:
-
M、P、G 模型:
- M(Machine): 代表一个操作系统线程。
- P(Processor): 管理可运行的 goroutine 队列。Go 运行时会创建一个固定数量的 P,默认与 CPU 核心数相同。
- G(Goroutine): Go 语言中的轻量级线程。
-
调度过程:
- 每个 P 维护一个本地的 goroutine 队列。
- M 需要与 P 绑定才能执行 goroutine。
- 调度器会选择一个 P,并将其与一个空闲的 M 绑定,然后从 P 的队列中取出 goroutine 执行。
-
工作窃取:
- 如果一个 P 的队列为空,它可以从其他 P 的队列中窃取 goroutine,以保持负载均衡。
-
阻塞处理:
- 当一个 goroutine 阻塞时(例如等待 I/O),M 会将其他可运行的 goroutine 调度到其他空闲的 M 上执行。
-
系统监控:
- 调度器会定期监控系统状态,调整 M 和 P 的数量,以优化性能。
这种设计使得 Go 的调度程序能够高效地管理大量 goroutine,充分利用多核 CPU 的能力。
M、P、G 的关系并不是完全在一个线程中
在 Go 的调度模型中,M、P、G 的关系并不是完全在一个线程中,而是通过以下方式协同工作:
-
M(Machine):
- 代表一个操作系统线程。
- 负责实际执行 goroutine。
-
P(Processor):
- 代表执行 goroutine 所需的资源。
- 每个 P 维护一个本地的 goroutine 队列。
- P 必须与 M 绑定,M 才能执行 P 中的 goroutine。
-
G(Goroutine):
- Go 语言中的轻量级线程。
- 在 P 的队列中排队等待执行。
工作流程:
- M 需要与一个 P 绑定才能执行 G。
- P 管理 G 的调度,M 负责执行。
- 如果一个 goroutine 阻塞,M 会解除与 P 的绑定,去处理其他任务,而 P 会继续调度其他 G。
这种设计允许 Go 运行时在 多个线程上高效地调度和执行大量 goroutine。
Go 的调度模型图

在这个图中:
- OS Thread: 对应于 M(Machine),即操作系统线程,负责执行 goroutine。
- Local Run Queue: 对应于 P(Processor)的本地队列,管理和调度 goroutine(G)。
图示展示了 Go 调度程序如何在多个 OS 线程(M)上调度和执行 goroutine(G),并通过本地运行队列(P)来管理这些 goroutine。
在 Go 的调度模型中:
- Global Run Queue:
- 是一个全局的 goroutine 队列。
- 当本地运行队列(P 的队列)没有可执行的 goroutine 时,M 可以从全局运行队列中获取 goroutine。
- 用于平衡负载,确保 goroutine 能被及时调度和执行。
全局运行队列帮助在多个 P 之间分配 goroutine,防止某些 P 过载而其他 P 空闲。
新的 goroutine 被创建时
在 Go 中,当一个新的 goroutine 被创建时,它通常会被直接放入当前 P(Processor)的本地运行队列,而不是全局运行队列。只有在特定情况下,例如负载不均衡或本地队列为空时,调度器才会使用全局运行队列来获取或分配 goroutine。全局运行队列主要 用于在多个 P 之间进行负载均衡。