Go Goroutine与并发模型详解
理解GMP调度模型是掌握Go并发编程的核心。
GMP模型组成
三大组件
Go
G:Goroutine(协程)
M:Machine(OS线程)
P:Processor(处理器/调度资源)
关系示意
Go
┌─────────────────────────────────────┐
│ 全局G队列 │
│ (备选G来源) │
└─────────────────────────────────────┘
↓
┌──────────────────┐ ┌──────────────────┐
│ P │ │ P │
│ 本地G队列[256] │ │ 本地G队列[256] │
│ mcache │ │ mcache │
└──────────────────┘ └──────────────────┘
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ M │ │ M │
│ 执行绑定的G │ │ 执行绑定的G │
└──────────────────┘ └──────────────────┘
P的作用
Go
type p struct {
runq [256]*g // 本地G队列
mcache *mcache // 内存缓存
status uint32 // 状态
}
// P数量 = GOMAXPROCS
// 默认 = CPU核心数
runtime.GOMAXPROCS(4) // 设置4个P
G的状态流转
主要状态
Go
const (
_Gidle // 刚分配
_Grunnable // 在队列等待执行
_Grunning // 正在执行
_Gsyscall // 系统调用中
_Gwaiting // 被挂起(I/O/channel等)
_Gdead // 已终止
)
状态转换
Go
创建 → Runnable → Running → Systemcall → Runnable → Running → Dead
↓ ↑
Waiting(I/O/channel/select)
调度策略
1. 本地队列优先
Go
func schedule() {
// 优先从P本地队列获取G
g := runqget(currentP)
if g == nil {
// 本地空,找其他来源
g = findrunnable()
}
execute(g)
}
2. 全局队列备选
Go
func findrunnable() *g {
// 本地空后检查全局队列
g := globrunqget()
if g != nil {
return g
}
// ...
}
3. 工作窃取
Go
func findrunnable() *g {
// 从其他P偷取G
for i := 0; i < nproc; i++ {
p := allp[(currentP.id+i+1)%nproc]
g := runqsteal(currentP, p)
if g != nil {
return g
}
}
}
4. Net Poller
Go
func findrunnable() *g {
// 检查网络I/O就绪的G
g := netpoll()
if g != nil {
return g
}
}
M与P的绑定
执行要求
Go
// M必须有P才能执行G
// M + P → 执行G
type m struct {
p *p // 绑定的P
curg *g // 当前执行的G
}
系统调用时解绑
Go
// M进入系统调用时
// 1. M状态 → syscall
// 2. P释放(handoff)
// 3. 其他M可绑定这个P
// 示例:
M1 + P1 → 执行G1
G1 → 文件I/O(syscall)
M1释放P1,进入syscall
M2绑定P1,执行G2
Goroutine创建与退出
创建
Go
go func() {
// 新G被创建
// 状态:Runnable
// 进入本地或全局队列
}()
退出
Go
go func() {
// 执行完毕
// 状态:Dead
// G被回收
}()
调度相关参数
GOMAXPROCS
Go
// 控制P数量
runtime.GOMAXPROCS(8) // 8个P并行
// 默认 = CPU核心数
// 建议:等于CPU核心数
调度锁定
text
// 锁定M到当前OS线程
runtime.LockOSThread()
// 解锁
runtime.UnlockOSThread()
// 用于:CGO、线程本地存储
获取goroutine信息
text
// 获取当前goroutine ID(非官方API)
// 不推荐依赖goroutine ID
// 获取goroutine数量
runtime.NumGoroutine()
调度监控
sysmon后台线程
text
// sysmon是特殊M:
// - 不需要P
// - 后台运行
// - 检查系统调用时长
// - 强制抢占
// - 检查GC需求
调度流程总结
text
┌─────────────────────────────────────┐
│ schedule() │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 查找可执行的G │
│ 优先级:本地→全局→窃取→NetPoller │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ execute(g) │
│ G状态:Runnable → Running │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 执行G │
│ 阻塞/系统调用时G状态变化 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ goexit() │
│ G状态:Running → Dead │
│ 回收G │
└─────────────────────────────────────┘
要点总结
- GMP模型:G执行任务,M是OS线程,P是调度资源
- P数量 = GOMAXPROCS,默认CPU核心数
- P本地队列256容量,无锁快速调度
- 工作窃取实现负载均衡
- 系统调用时M释放P
- sysmon后台监控和抢占
- G状态:Runnable→Running→Waiting→Dead
- 用GOMAXPROCS控制并行度
📝 发现内容有误?点击此处直接编辑