Files
cs-note/hzh/GolangStar/Go语言原理/gmp调度原理/gmp-datastructures.md
T

4.2 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go-principle
gmp-scheduler
datastructure
2026-06-07 16:00

GMP 调度原理 — 数据结构

概述

本节深入 runtime/runtime2.go,逐字段拆解 G、M、P、schedt 四个核心结构体的源码定义及其设计意图。理解这些结构是后续追踪调度流程的基础。

正文

2.1 G(Goroutine)

G 是 goroutine 的运行时实体,承载栈空间、执行状态和生命周期信息。

type g struct {
    stack       stack        // 栈空间 [stack.lo, stack.hi)
    stackguard0 uintptr      // 栈保护区边界;也用于传递抢占标记

    _panic       *_panic     // panic 链表头
    _defer       *_defer     // defer 链表头(LIFO)
    m            *m          // 当前绑定的 M(未运行时为 nil)
    atomicstatus uint32      // 原子状态:_Gidle → _Grunnable → _Grunning → ...

    schedlink guintptr      // 全局队列 / 空闲链表中的 next 指针
}
关键字段 说明
stack Goroutine 的执行栈,初始约 2KB,可动态扩容
stackguard0 函数调用前比较值。若等于 stackPreempt 表示被标记抢占;若接近 stack.lo 则触发栈扩容
atomicstatus 生命周期状态的原子快照,通过 casgstatus() 切换

状态流转:

stateDiagram-v2
    [*] --> _Gidle: 未初始化
    _Gidle --> _Grunnable: newproc()
    _Grunnable --> _Grunning: schedule() 从队列取出
    _Grunning --> _Grunnable: Gosched() / preempt()
    _Grunning --> _Gdead: 执行完毕
    _Grunning --> _Gwaiting: gopark() 阻塞
    _Gwaiting --> _Grunnable: goready() 唤醒
    _Gwaiting --> _Gsyscall: (间接)
    _Gsyscall --> _Grunning: exitsyscall()
    _Gdead --> [*]: 回收
    style _Grunning fill:#e8f5e9
    style _Gwaiting fill:#fff3e0

2.2 M(Machine)

M 是 OS 线程的运行时封装,真正执行代码。

type m struct {
    g0       *g        // 调度协程,每个 M 独有
    procid   uint64    // M 的唯一 ID
    gsignal  *g        // 信号处理协程
    curg     *g        // 当前正在运行的用户 G
    p        puintptr  // 当前绑定的 P
    schedlink muintptr // 空闲 M 链表 next 指针
}

M 在两个角色间切换:

  • 执行 g0 时:调度者——调用 schedule() 寻找下一个待执行的 G
  • 执行 curg 时:执行者——运行用户代码

2.3 P(Processor)

P 是逻辑处理器,作为调度器的核心组件管理本地队列。

type p struct {
    id          int32        // P 的编号
    status      uint32       // _Pidle / _Prunning / _Psyscall
    link        puintptr     // 空闲 P 链表
    schedtick   uint32       // 每次 schedule() 自增
    syscalltick uint32       // 每次系统调用自增
    m           muintptr     // 回指绑定的 M(idle 时为 0)

    runqhead    uint32       // LRQ 头部索引
    runqtail    uint32       // LRQ 尾部索引
    runq        [256]guintptr // 本地 G 队列(环形数组)
    runnext     guintptr     // VIP 位置:高优先级下一个 G
}
关键字段 作用
runq[256] 定长环形数组作为 LRQ,CAS 无锁存取
runnext 新创建的 G 优先放入此处,下次调度直接执行,跳过队列开销
schedtick 配合防饥饿机制:schedtick % 61 == 0 时检查 GRQ

2.4 schedt(全局调度器)

schedt 管理跨 P 的全局资源,访问需持有 sched.lock。

type schedt struct {
    lock      mutex         // 全局互斥锁
    midle     muintptr      // 空闲 M 队列
    pidle     puintptr      // 空闲 P 队列
    runq      gQueue        // 全局 G 队列(GRQ)
    runqsize  int32         // GRQ 中 G 的数量
    // ...
}

[!note] 📝 idle 队列的设计意图 midle 和 pidle 实现了资源的休眠与复用——不忙时释放回池中,有新任务时快速唤醒,避免 CPU 空转或频繁创建线程。

关联笔记