Go GMP

93 阅读4分钟

GMP(Goroutine、Machine、Processor)是 Go 语言中用于实现并发的一种调度模型

1. GMP 模型概述

GMP 是 Go 调度器的核心,用于管理 goroutine系统线程(或者说机器的逻辑 CPU)。这个模型的设计旨在高效地调度并发任务,最大化并行度,同时最小化上下文切换的开销。

  • Goroutine(G) :轻量级线程,由 Go 运行时(runtime)管理。每个 goroutine 是独立的执行单元,Go 程序的并发执行通过 goroutine 实现。
  • Machine(M) :表示一个物理线程或操作系统线程,负责执行 goroutine。每个 M 与操作系统的线程一一对应。
  • Processor(P) :表示一个逻辑处理器,是一个调度单元。P 为 goroutine 分配任务并管理它们,P 数量通常与系统的 CPU 核数相等(可以配置更多的 P 数量,具体取决于硬件和调度需求)。

2. GMP 的工作原理

Go 的调度器通过以下几个步骤来管理并调度 goroutine:

  1. Goroutine(G)

    • 每个 goroutine 是一个由 Go 运行时管理的轻量级线程。Go 调度器通过将 goroutine 分配给机器(M)来执行任务。
    • 每个 goroutine 都会有一个自己的栈,Go 会动态扩展或收缩栈的大小。
  2. Machine(M)

    • M 是操作系统的线程,它负责执行 goroutine。
    • M 由 P 调度,并且 M 与 P 紧密绑定。每个 M 在执行 goroutine 时需要一个 P 来管理任务的分配。
    • M 会被创建和销毁以适应需要执行的 goroutine 数量。
  3. Processor(P)

    • P 是 Go 调度器中的逻辑处理单元,负责将 goroutine 分配给机器 M。
    • 每个 P 上有一个 本地运行队列(run queue),存储待执行的 goroutine。
    • 系统的 P 数量通常会等于或小于机器的 CPU 核数。P 的数量可以通过 GOMAXPROCS 环境变量配置。
    • 每个 P 只能与一个 M 绑定,并且每个 M 只能同时执行一个 P 上的 goroutine。

3. 调度过程

  • Goroutine 创建与调度

    • 当创建一个新的 goroutine 时,调度器会将它放入一个合适的 P 的运行队列(run queue)中。
    • 如果有空闲的 M,调度器会将 goroutine 分配给 M 执行。否则,它会等到有空闲的 M 来处理该 goroutine。
  • P 和 M 的关系

    • 一个 P 绑定一个 M,负责分配和调度 goroutine。如果 P 运行队列中有 goroutine,P 会把它交给绑定的 M 执行。
    • 如果一个 M 空闲,且有 P 可以调度,它会尝试从其他 P 中偷取 goroutine 来执行。这种机制叫做 负载均衡
  • 调度的抢占与切换

    • 如果一个 goroutine 执行时间过长,调度器会进行抢占,允许其他 goroutine 执行。
    • Go 的调度器基于时间片、I/O 操作、协作式调度等方式进行上下文切换,以确保公平性。

4. 调度的优化与特性

  • Goroutine 本地队列和负载均衡:每个 P 上有自己的本地队列来存储 goroutine,避免了全局队列的竞争问题。如果某个 P 的队列空了,它可以从其他 P 上偷取 goroutine,保证了高效的负载均衡。
  • 减少阻塞:当 goroutine 阻塞时(比如等待 I/O 或系统调用),调度器会将它挂起,并让其他 goroutine 执行。这保证了系统资源的高效利用。

5. 总结

GMP 模型通过结合 goroutine、机器(操作系统线程)和处理器(逻辑处理器)的协调,提供了高效的并发调度。通过使用轻量级的 goroutine 和灵活的任务分配策略,Go 可以实现大规模并发程序的高效调度,而无需过多的上下文切换和线程管理开销。

Go 语言的调度器采用了 GMP 模型,包含了三部分:Goroutine(G)、Machine(M)和Processor(P)。Goroutine 是轻量级线程,Machine 是操作系统线程,Processor 是调度单元。调度器将 goroutine 分配到 M 上执行,并通过 P 来管理和调度任务。通过本地队列和负载均衡,Go 的调度器能够高效地管理大量的并发任务,同时尽量减少上下文切换的开销。通过合理配置 P 数量,可以最大化 CPU 使用率。”