Skip to content

GPM

Go Goroutine 模型

Goroutine 是 Go 并发编程的核心概念之一。它是一种轻量级的线程,由 Go 运行时(Go runtime)管理,而不是直接由操作系统管理。Goroutine 在 Go 中用于执行并发任务,并能够通过 Go 调度器高效地调度执行。

1. Goroutine 基本概念

  • Goroutine 是 Go 程序中的轻量级线程,使用 go 关键字启动。
  • 每个 Goroutine 都有自己的栈,初始时栈非常小(大约 2 KB),并会根据需要动态扩展或收缩。
  • Goroutine 由 Go 调度器管理,调度器负责将 Goroutine 映射到操作系统线程上。

2. Goroutine 调度模型(GPM 模型)

Go 使用一种叫做 GPM(Goroutine、Processor、Machine)的调度模型。该模型通过 Go 调度器管理 Goroutine 的执行。

  • G(Goroutine):是 Go 程序中的最小执行单位,每个 Goroutine 表示一个独立的任务。
  • P(Processor):处理器,负责执行 Goroutine,调度 Goroutine 到 M 上执行。每个 P 可以调度多个 G,P 的数量通常设置为 CPU 核心数(GOMAXPROCS)。
  • M(Machine):机器,表示操作系统线程。Go 调度器将 P 和 M 关联起来,将 Goroutine 分配到 M 上执行。
GPM 调度模型图示:
+---------+        +---------+        +---------+
|    G    |        |    G    |        |    G    |
|  Goroutine  |   -->  Goroutine  |   -->  Goroutine  |
+---------+        +---------+        +---------+
         |                |                  |
    +----v----+     +----v----+       +----v----+
    |    P    |     |    P    |       |    P    |
    | Processor |     | Processor |     | Processor |
    +----+----+     +----+----+       +----+----+
         |                |                  |
    +----v----+     +----v----+       +----v----+
    |    M    |     |    M    |       |    M    |
    |   OS Thread   |     |   OS Thread   |     |   OS Thread   |
    +-------------+     +-------------+     +-------------+
  • 每个 P 有一个本地队列用于存储待执行的 Goroutine(G)。
  • 每个 M 是一个实际的操作系统线程,执行 P 分配给它的任务。
  • Go 调度器会动态地调度 Goroutine 到不同的 M 上执行。

3. Goroutine 的特点

  • 轻量级:Goroutine 相比操作系统线程非常轻量,一个程序可以同时创建数百万个 Goroutine,而不会造成太大的性能开销。
  • 自动扩展栈:Goroutine 初始时栈非常小(约 2 KB),随着 Goroutine 执行的需要,栈大小会动态增长,最大可扩展到 1 MB。
  • 高效调度:Go 调度器会根据 Goroutine 的数量和系统资源动态调整执行,最大限度地利用多核处理器。

4. Goroutine 的生命周期

  • 创建:通过 go 关键字启动 Goroutine,例如 go f()
  • 执行:Goroutine 会在调度器的控制下开始执行。
  • 阻塞/等待:Goroutine 在等待 IO 操作、接收通道数据或进行其他阻塞操作时会被挂起。
  • 终止:Goroutine 完成任务后会退出,Go 运行时会回收资源。

5. Goroutine 的调度

  • Go 的调度器是基于抢占式调度的,允许 Goroutine 在 CPU 上共享时间片。
  • 调度器会定期抢占正在执行的 Goroutine,并将 CPU 时间片分配给其他 Goroutine。
  • 调度器通过监控 Goroutine 的状态来决定哪个 Goroutine 应该执行。例如,如果一个 Goroutine 在等待通道数据,调度器会将它挂起,并激活其他准备好的 Goroutine。

6. Goroutine 与操作系统线程

  • 每个操作系统线程(M)可以同时执行多个 Goroutine。
  • Goroutine 是由 Go 调度器管理的,并不直接映射到操作系统线程。调度器可以根据需要将 Goroutine 重新调度到不同的线程上执行。

7. Goroutine 示例

go
package main

import "fmt"

// 一个简单的 Goroutine 示例
func sayHello() {
    fmt.Println("Hello from Goroutine!")
}

func main() {
    go sayHello()  // 启动一个 Goroutine
    fmt.Println("Hello from main!")
}

输出(执行顺序可能不同):

Hello from main!
Hello from Goroutine!

8. Goroutine 与 Channel 的协作

Goroutine 通常与 Channel 一起使用,以便 Goroutine 之间进行通信。例如,通过 Channel 向其他 Goroutine 发送结果或状态。

go
package main

import "fmt"

func sum(a, b int, ch chan int) {
    result := a + b
    ch <- result  // 将结果发送到通道
}

func main() {
    ch := make(chan int)
    go sum(1, 2, ch)  // 启动 Goroutine
    result := <-ch    // 从通道接收结果
    fmt.Println("Result:", result)
}

输出:

Result: 3

9. Goroutine 使用注意事项

  • 资源管理:Goroutine 创建后需要合理管理,避免泄漏。使用 sync.WaitGroupselect 等机制来确保 Goroutine 执行完毕。
  • 并发问题:当多个 Goroutine 访问共享资源时,需要使用同步原语(如 sync.Mutex)来避免数据竞争。
  • 死锁:多个 Goroutine 在等待互相持有的资源时可能导致死锁,设计时应避免这种情况。

总结

Goroutine 是 Go 的并发编程核心,通过调度器高效地管理。它的轻量级特性和高效的调度机制使得 Go 在并发场景中非常适合处理大量并行任务。通过与 Channel、WaitGroup 等工具结合使用,Goroutine 可以实现更高效、更可靠的并发处理。