Featured image of post Go-GMP模型理解

Go-GMP模型理解

快速了解Go-GMP模型

Go 语言 GMP 模型理解

核心三层架构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
┌─────────────────────────────────────────────────┐
│           G (Goroutine) - 用户级线程            │
│  轻量级、海量(百万级)、由Go调度器管理           │
└────────────┬────────────────────────────────────┘
             │ 绑定关系
┌────────────▼────────────────────────────────────┐
│     M (Machine) - OS线程 / 工作线程             │
│  真实执行单位、数量受限(默认10000)、昂贵资源    │
└────────────┬────────────────────────────────────┘
             │ 绑定关系
┌────────────▼────────────────────────────────────┐
│      P (Processor) - 逻辑处理器 / 调度器        │
│  本地队列持有者、数量=GOMAXPROCS(默认=CPU核数)  │
└─────────────────────────────────────────────────┘

快速记忆法

层级英文中文数量特性
GGoroutine协程百万+用户态、廉价、可阻塞
MMachine线程~10K内核态、昂贵、真执行
PProcessor处理器CPU核数调度器、本地队列

执行流程(3步)

1
2
3
4
5
1. G创建 → 进入P的本地队列(LRQ)
   
2. M从P获取G → 执行(M:P = N:1)
   
3. G阻塞/完成 → P寻找新G或唤醒M

关键机制

1. Work Stealing(工作窃取)

1
2
3
4
P1本地队列满        P2本地队列空
    ↓                   ↓
[G1][G2][G3]    →   [G1][G2]
P1将G3转移给P2(负载均衡)

当P2队列为空的时候,会从P1偷走一半(向上取整)

2. 自旋M(Spinning M)

  • M没有G时不立即休眠,而是自旋寻找G
  • 减少M唤醒延迟,提高吞吐量
  • 最多GOMAXPROCS个M自旋

3. 阻塞处理

1
2
3
4
5
6
7
G阻塞(I/O/锁)
M与P解绑 → M进入阻塞等待
P立即绑定新M → 继续执行队列中的G
原M唤醒 → 寻找可用P或加入全局队列

常见问题速解

Q: 为什么需要P?
A: 避免全局队列锁竞争,P的本地队列无锁。

Q: M数量为什么有上限?
A: 每个M占用~2MB内存,防止内存爆炸。

Q: GOMAXPROCS=1时会怎样?
A: 单P单M,所有G串行执行(除非G主动让出)。

Q: 如何观察GMP状态?

1
2
GODEBUG=gctrace=1 ./program  # 查看GC与调度信息
go tool trace trace.out       # 可视化调度

代码示例:观察GMP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	// 设置P数量
	runtime.GOMAXPROCS(2)

	// 打印当前GMP状态
	var m runtime.MemStats
	runtime.ReadMemStats(&m)

	fmt.Printf("NumGoroutine: %d\n", runtime.NumGoroutine())
	fmt.Printf("NumCPU: %d\n", runtime.NumCPU())
	fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(-1))

	// 创建大量G
	for i := range 100 {
		go func(id int) {
			time.Sleep(100 * time.Millisecond)
			fmt.Printf("G%d done\n", id)
		}(i)
	}

	time.Sleep(200 * time.Millisecond)
}

调度决策树

1
2
3
4
5
6
7
G就绪
  ├─ P有空闲M? ──Yes→ 绑定执行
  └─ No
      ├─ 其他P有G? ──Yes→ Work Stealing
      └─ No → G进入全局队列(GRQ)

参考

使用 Hugo 构建
主题 StackJimmy 设计