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

Go Goroutine与GMP模型

Go调度器通过GMP模型实现轻量级并发,用户级调度高效切换。

GMP模型概述

三大组件

Go
G(Goroutine):用户协程
M(Machine):操作系统线程
P(Processor):调度处理器(逻辑核心)

关系示意

Go
   G队列
     ↓
   P(调度器)← 本地队列
     ↓
   M(执行线程)
     ↓
   CPU核心

P的数量默认等于CPU核心数(GOMAXPROCS)。

G(Goroutine)

结构定义

Go
type g struct {
    stack       stack     // 栈空间(2KB起步)
    sched       gobuf     // 调度上下文(PC、SP)
    status      uint32    // 状态
    goid        int64     // Goroutine ID

    m           *m        // 绑定的M
    atomicstatus uint32   // 原子状态
}

type gobuf struct {
    sp   uintptr  // 栈指针
    pc   uintptr  // 程序计数器
    g    uintptr  // Goroutine指针
}

G状态表

状态含义说明
_Gidle初始状态刚分配
_Grunnable可运行在队列等待
_Grunning正在运行被M执行
_Gwaiting等待状态阻塞中
_Gsyscall系统调用执行syscall
_Gdead结束状态已退出

M(Machine)

结构定义

Go
type m struct {
    g0          *g        // 特殊G(调度栈)
    curg        *g        // 当前执行的G
    p           *p        // 绑定的P
    nextp       *p        // 下一个P
    spinning    bool      // 是否自旋寻找G

    park        note      // 休眠通知
    parknote    *note     // 休眠条件
}

M状态

Go
运行中:绑定P执行G
自旋:寻找可执行G(无G但有P)
休眠:无G无P,等待唤醒

g0是M的调度栈,用于执行调度代码。

P(Processor)

结构定义

Go
type p struct {
    id          int32     // P的ID
    status      uint32    // 状态
    m           *m        // 绑定的M

    // 本地G队列
    runq        [256]*g   // 本地队列
    runqhead    uint32    // 队头
    runqtail    uint32    // 队尾

    runnext     *g        // 优先执行的G
    gFree       *g        // G缓存池
}

P状态表

状态含义说明
_Pidle空闲无G可执行
_Prunning运行绑定M执行G
_Psyscall系统调用M在syscall中
_PgcstopGC停止GC时暂停
_Pdead结束不再使用

调度流程

schedule函数

Go
func schedule() {
    // 1. 检查runnext(优先)
    if gp := runnext; gp != nil {
        execute(gp)
        return
    }

    // 2. 从本地队列获取
    gp := runqget()
    if gp != nil {
        execute(gp)
        return
    }

    // 3. 从全局队列获取
    gp := globrunqget()
    if gp != nil {
        execute(gp)
        return
    }

    // 4. 工作窃取
    gp := stealWork()
    if gp != nil {
        execute(gp)
        return
    }

    // 5. 没有G则休眠
    park()
}

调度优先级

Go
runnext(刚唤醒的G)
    ↓ 最高优先
本地队列(runq)
    ↓
全局队列(globrunq)
    ↓
工作窃取(其他P的队列)
    ↓
netpoller(网络就绪G)
    ↓
休眠等待

工作窃取

算法原理

Go
func stealWork() *g {
    // 随机选择其他P
    p2 := allp[fastrand()%n]

    // 窃取一半G
    n := runqsteal(p2, p)
    if n > 0 {
        return stolenG
    }

    return nil
}

窃取示意

Go
P1队列:G1 G2 G3 G4
    ↓ 窃取一半
P2队列:G1 G2(偷来)

工作窃取实现负载均衡,避免部分P过忙。

系统调用处理

entersyscall

Go
func entersyscall() {
    // P状态改为_Psyscall
    P.status = _Psyscall

    // M解绑P(允许其他M接管)
    // 如果syscall超过10ms
    // sysmon会handoffp
}

exitsyscall

Bash
func exitsyscall() {
    // 尝试重新获取原P
    if tryReacquireP() {
        // 成功继续执行
        P.status = _Prunning
        return
    }

    // 失败:G放入全局队列
    // M进入休眠
    globrunqput(g)
    parkm()
}

长时间syscall会释放P,避免阻塞其他G。

抢占调度

基于信号抢占

Go
// Go 1.14+基于信号的异步抢占

func preemptone(p) {
    // 发送SIGURG信号
    signalM(m, sigPreempt)

    // M收到信号后
    // 在安全点暂停G
    // 调度执行其他G
}

抢占触发条件

text
G运行超过10ms
    ↓ sysmon检测
发送抢占信号
    ↓
G在安全点暂停
    ↓
重新调度

防止一个G长时间占用CPU,实现公平调度。

Goroutine创建

newproc流程

text
func newproc(fn) {
    // 1. 获取或创建G
    newg := gfget()
    if newg == nil {
        newg = malg()  // 新建G
    }

    // 2. 初始化G
    newg.status = _Grunnable
    newg.sched.pc = fn

    // 3. 放入队列
    runqput(p, newg)

    // 4. 尝试唤醒M
    if atomicload(&m.spinning) == 0 {
        wakep()
    }
}

G栈管理

text
初始栈:2KB
    ↓ 动态增长
最大栈:1GB(64位)
    ↓ 栈收缩
释放后:复用或回收

Goroutine栈动态伸缩,相比线程栈节省内存。

调度器配置

GOMAXPROCS

text
import "runtime"

// 设置P数量(CPU核心数)
runtime.GOMAXPROCS(4)

// 获取当前设置
n := runtime.GOMAXPROCS(0)

NumGoroutine

text
// 获取当前Goroutine数量
n := runtime.NumGoroutine()
fmt.Println(n)  // 活跃G数量

调度信息查看

GODEBUG调试

text
# 输出调度信息
GODEBUG=schedtrace=1000 ./app

# 输出示例
SCHED 1000ms: gomaxprocs=8 idleprocs=6 threads=10 ...

schedtrace字段

text
gomaxprocs:P数量
idleprocs:空闲P数量
threads:M总数
idlethreads:空闲M数量
runnablequeue:全局队列G数
runnable:总可运行G数

handoff机制

sysmon监控

text
func sysmon() {
    // 每10ms检查一次

    // 1. 检查抢占
    retakeP()

    // 2. 检查系统调用
    if p.status == _Psyscall && elapsed > 10ms {
        handoffp(p)  // P交给其他M
    }

    // 3. 检查netpoller
    netpoll()
}

调度器核心机制表

机制作用场景
工作窃取负载均衡P空闲时
系统调用handoffP释放syscall阻塞
基于信号抢占防止饿死G运行过长
netpoller网络I/O非阻塞I/O
runnext优先调度刚唤醒G

要点总结

  • G是Goroutine,M是线程,P是调度处理器
  • P数量默认等于CPU核心数
  • 本地队列实现无锁调度
  • 工作窃取实现负载均衡
  • entersyscall/exitsyscall处理系统调用
  • sysmon监控抢占和handoff
  • 基于信号实现异步抢占
  • runnext优先调度刚唤醒的G
  • GOMAXPROCS设置P数量
  • NumGoroutine获取活跃G数
  • G栈动态伸缩,初始2KB

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

← 上一篇 Go垃圾回收机制
下一篇 → Go接口与类型系统
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

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

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