全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-13 8 分钟 ✍️ juanwangdev

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/Osyscall
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控制并行度

📝 发现内容有误?点击此处直接编辑

← 上一篇 Go Context上下文控制详解
下一篇 → Go channel类型与通信详解
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库