Goroutine调度 #
一、调度器概述 #
Go调度器负责将Goroutine分配到CPU上执行。
二、GMP模型 #
2.1 G (Goroutine) #
- 协程,用户态线程
- 包含栈、指令指针等信息
- 初始栈2KB,可增长
2.2 M (Machine) #
- 操作系统线程
- 执行G的载体
- 数量默认最多10000
2.3 P (Processor) #
- 逻辑处理器
- 包含运行队列
- 默认数量等于CPU核心数
2.4 关系图 #
text
全局队列
│
▼
┌───┬───┬───┬───┐
│ P │ P │ P │ P │ ← 处理器
└─┬─┴─┬─┴─┬─┴─┬─┘
│ │ │ │
▼ ▼ ▼ ▼
┌─┴─┬─┴─┬─┴─┬─┴─┐
│ M │ M │ M │ M │ ← 系统线程
└───┴───┴───┴───┘
三、调度原理 #
3.1 工作窃取 #
当P的本地队列为空时,从其他P窃取G:
text
P1: [G1, G2, G3] P2: []
│ │
└────窃取────┘
P1: [G1, G2] P2: [G3]
3.2 手递手传递 #
新创建的G优先放入本地队列:
go
go func() {} // 放入当前P的本地队列
3.3 系统调用 #
当G进行系统调用时,M会阻塞,P会绑定到新的M:
text
G → 系统调用 → M阻塞 → P解绑 → 绑定新M → 继续执行
四、调度策略 #
4.1 调度时机 #
- goroutine创建
- goroutine退出
- 系统调用
- channel阻塞
- time.Sleep
- 主动让出
4.2 抢占式调度 #
Go 1.14+支持异步抢占:
go
func infiniteLoop() {
for {
// 可能被抢占
}
}
4.3 公平调度 #
- 本地队列:FIFO
- 全局队列:定期检查
- 系统调用返回的G优先
五、调度器配置 #
5.1 GOMAXPROCS #
go
runtime.GOMAXPROCS(4) // 设置P的数量
5.2 查看调度信息 #
go
fmt.Println(runtime.GOMAXPROCS(0)) // P的数量
fmt.Println(runtime.NumCPU()) // CPU核心数
fmt.Println(runtime.NumGoroutine()) // G的数量
5.3 调试信息 #
go
import "runtime/debug"
debug.SetMaxThreads(100) // 设置最大线程数
六、调度跟踪 #
6.1 GODEBUG #
bash
GODEBUG=schedtrace=1000 go run main.go
输出示例:
text
SCHED 1000ms: gomaxprocs=8 idleprocs=0 threads=10 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0 0 0 0 0]
6.2 go tool trace #
go
import (
"os"
"runtime/trace"
)
func main() {
f, _ := os.Create("trace.out")
defer f.Close()
trace.Start(f)
defer trace.Stop()
// 程序代码
}
分析:
bash
go tool trace trace.out
七、调度器优化 #
7.1 避免频繁创建goroutine #
go
// 使用worker pool
type WorkerPool struct {
tasks chan func()
}
func NewWorkerPool(n int) *WorkerPool {
p := &WorkerPool{
tasks: make(chan func(), 100),
}
for i := 0; i < n; i++ {
go p.worker()
}
return p
}
func (p *WorkerPool) worker() {
for task := range p.tasks {
task()
}
}
func (p *WorkerPool) Submit(task func()) {
p.tasks <- task
}
7.2 控制并发数 #
go
func processWithLimit(items []int, limit int) {
sem := make(chan struct{}, limit)
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
sem <- struct{}{}
go func(item int) {
defer wg.Done()
defer func() { <-sem }()
process(item)
}(item)
}
wg.Wait()
}
7.3 避免阻塞 #
go
// 使用非阻塞操作
select {
case ch <- data:
// 发送成功
default:
// 避免阻塞
}
八、调度器调优 #
8.1 CPU密集型 #
go
runtime.GOMAXPROCS(runtime.NumCPU())
8.2 IO密集型 #
go
// 增加goroutine数量
// 调度器会自动处理
8.3 混合型 #
go
// 根据实际情况调整
runtime.GOMAXPROCS(runtime.NumCPU())
九、调度器问题 #
9.1 goroutine泄漏 #
go
func leak() {
ch := make(chan int)
go func() {
<-ch // 永远阻塞
}()
}
9.2 调度延迟 #
go
// 避免长时间占用CPU
for {
// 定期让出
runtime.Gosched()
}
9.3 线程过多 #
go
// 检查线程数
fmt.Println(runtime.ThreadCreateProfile(nil))
十、最佳实践 #
10.1 合理设置GOMAXPROCS #
go
// 容器环境
runtime.GOMAXPROCS(runtime.NumCPU())
10.2 使用worker pool #
go
// 复用goroutine
pool := NewWorkerPool(10)
10.3 避免goroutine泄漏 #
go
// 使用context取消
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
select {
case <-ctx.Done():
return
case <-ch:
// 处理
}
}()
十一、总结 #
调度器要点:
| 组件 | 说明 |
|---|---|
| G | Goroutine |
| M | 系统线程 |
| P | 处理器 |
关键点:
- GMP模型:理解G、M、P的关系
- 工作窃取:负载均衡机制
- 抢占调度:Go 1.14+支持异步抢占
- 调度跟踪:使用trace工具分析
- 性能调优:根据场景调整配置
准备好学习sync包了吗?让我们进入下一章!
最后更新:2026-03-26