Skip to main content

Go 调度程序的工作原理

Go 调度程序的工作原理

Go 调度程序负责在多个操作系统线程上高效地调度和执行 goroutine。以下是其工作原理的关键点:

  1. M、P、G 模型:

    • M(Machine): 代表一个操作系统线程。
    • P(Processor): 管理可运行的 goroutine 队列。Go 运行时会创建一个固定数量的 P,默认与 CPU 核心数相同。
    • G(Goroutine): Go 语言中的轻量级线程。
  2. 调度过程:

    • 每个 P 维护一个本地的 goroutine 队列。
    • M 需要与 P 绑定才能执行 goroutine。
    • 调度器会选择一个 P,并将其与一个空闲的 M 绑定,然后从 P 的队列中取出 goroutine 执行。
  3. 工作窃取:

    • 如果一个 P 的队列为空,它可以从其他 P 的队列中窃取 goroutine,以保持负载均衡。
  4. 阻塞处理:

    • 当一个 goroutine 阻塞时(例如等待 I/O),M 会将其他可运行的 goroutine 调度到其他空闲的 M 上执行。
  5. 系统监控:

    • 调度器会定期监控系统状态,调整 M 和 P 的数量,以优化性能。

这种设计使得 Go 的调度程序能够高效地管理大量 goroutine,充分利用多核 CPU 的能力。

M、P、G 的关系并不是完全在一个线程中

在 Go 的调度模型中,M、P、G 的关系并不是完全在一个线程中,而是通过以下方式协同工作:

  1. M(Machine):

    • 代表一个操作系统线程。
    • 负责实际执行 goroutine。
  2. P(Processor):

    • 代表执行 goroutine 所需的资源。
    • 每个 P 维护一个本地的 goroutine 队列。
    • P 必须与 M 绑定,M 才能执行 P 中的 goroutine。
  3. G(Goroutine):

    • Go 语言中的轻量级线程。
    • 在 P 的队列中排队等待执行。

工作流程:

  • M 需要与一个 P 绑定才能执行 G。
  • P 管理 G 的调度,M 负责执行。
  • 如果一个 goroutine 阻塞,M 会解除与 P 的绑定,去处理其他任务,而 P 会继续调度其他 G。

这种设计允许 Go 运行时在多个线程上高效地调度和执行大量 goroutine。

Go 的调度模型图

alt text

在这个图中:

  • 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 之间进行负载均衡。