--- tags: [go, golang, go-principle, gmp-scheduler, datastructure] create time: 2026-06-07 16:00 --- # GMP 调度原理 — 数据结构 ## 概述 本节深入 `runtime/runtime2.go`,逐字段拆解 G、M、P、schedt 四个核心结构体的源码定义及其设计意图。理解这些结构是后续追踪调度流程的基础。 ## 正文 ### 2.1 G(Goroutine) G 是 goroutine 的运行时实体,承载栈空间、执行状态和生命周期信息。 ```go 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()` 切换 | **状态流转**: ```mermaid 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 线程的运行时封装,真正执行代码。 ```go 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 是逻辑处理器,作为调度器的核心组件管理本地队列。 ```go 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`。 ```go type schedt struct { lock mutex // 全局互斥锁 midle muintptr // 空闲 M 队列 pidle puintptr // 空闲 P 队列 runq gQueue // 全局 G 队列(GRQ) runqsize int32 // GRQ 中 G 的数量 // ... } ``` > [!note] 📝 idle 队列的设计意图 > `midle` 和 `pidle` 实现了资源的休眠与复用——不忙时释放回池中,有新任务时快速唤醒,避免 CPU 空转或频繁创建线程。 ## 关联笔记 - [[hzh/GolangStar/Go语言原理/gmp调度原理/gmp-overview]] — GMP 概览 - [[hzh/GolangStar/Go语言原理/gmp调度原理/gmp-lifecycle]] — 创建与调度流程 - [[hzh/GolangStar/Go语言原理/gmp调度原理/gmp-preemption]] — 抢占机制