GMP 三大核心结构体字段详解

一、type g struct - Goroutine 结构体

1. 栈管理相关 (Stack Management)

go 复制代码
stack       stack   // 栈内存范围 [stack.lo, stack.hi)
stackguard0 uintptr // Go 栈增长检查点,通常是 stack.lo+StackGuard,也可设为 StackPreempt 触发抢占
stackguard1 uintptr // C 栈增长检查点,用于 g0 和 gsignal 栈
  • stack: 描述实际栈内存的上下界
  • stackguard0: 函数序言(prologue)中检查栈溢出的哨兵值,设为特殊值可触发抢占
  • stackguard1: 仅用于特殊 goroutine(g0、信号栈),防止 C 代码栈溢出

2. 异常处理链表

go 复制代码
_panic  *_panic // 最内层的 panic 链表头
_defer  *_defer // 最内层的 defer 链表头
  • 采用链表存储,支持嵌套的 panic/defer

3. 调度核心字段

go 复制代码
m         *m      // 当前绑定的 M(如果正在运行)
sched     gobuf   // 调度上下文:保存 SP、PC、G 等寄存器
syscallsp uintptr // 系统调用时的 SP,供 GC 使用
syscallpc uintptr // 系统调用时的 PC,供 GC 使用
stktopsp  uintptr // 栈顶 SP,用于 traceback 验证
  • sched: 核心!保存 goroutine 被调度出去时的 CPU 寄存器状态
  • syscallsp/pc: 系统调用期间,栈可能被扫描,需要记录位置

4. 状态与标识

go 复制代码
atomicstatus atomic.Uint32 // Goroutine 状态(_Gidle/_Grunnable/_Grunning/_Gsyscall/_Gwaiting/_Gdead...)
stackLock    uint32         // 栈操作锁,防止并发修改栈
goid         uint64         // Goroutine 唯一 ID
schedlink    guintptr       // 链表指针,用于构建运行队列

5. 阻塞追踪

go 复制代码
waitsince  int64      // G 阻塞的近似时间戳
waitreason waitReason // 阻塞原因(chan receive、sync.Mutex.Lock 等)

6. 抢占相关

go 复制代码
preempt       bool // 抢占信号(与 stackguard0 = stackpreempt 重复)
preemptStop   bool // 抢占时是否转为 _Gpreempted 状态
preemptShrink bool // 是否在安全点收缩栈
asyncSafePoint bool // 是否在异步安全点停止(栈上可能有非精确指针信息)
  • Go 1.14+ 异步抢占的关键标志位

7. 调试与 Profiling

go 复制代码
parentGoid uint64          // 创建此 goroutine 的父 goroutine ID
gopc       uintptr         // 创建此 goroutine 的 PC(go 语句位置)
startpc    uintptr         // goroutine 函数的 PC
ancestors  *[]ancestorInfo // 祖先信息(debug.tracebackancestors=1 时启用)

8. 同步与通道

go 复制代码
param   unsafe.Pointer // 通用参数指针:
                       // 1. channel 唤醒时指向 sudog
                       // 2. GC assist 完成信号
                       // 3. debugCallWrap 传参
waiting *sudog         // 此 G 正在等待的 sudog 链表

9. 性能追踪

go 复制代码
tracking      bool  // 是否追踪调度延迟统计
trackingSeq   uint8 // 追踪序列号
trackingStamp int64 // 开始追踪的时间戳
runnableTime  int64 // 在 _Grunnable 状态累计的时间

10. GC 辅助

go 复制代码
gcAssistBytes int64 // GC 辅助信用(正值=可分配字节,负值=需辅助扫描)
  • 控制 mutator 是否需要参与 GC 标记工作

二、type m struct - 机器线程结构体

1. 特殊 Goroutine

go 复制代码
g0      *g // 调度专用 goroutine(更大的栈,用于执行调度代码)
gsignal *g // 信号处理 goroutine
curg    *g // 当前正在运行的用户 goroutine
  • g0: 每个 M 都有,用于执行调度器、GC 等 runtime 代码
  • curg: 指向用户代码的 goroutine

2. P 绑定

go 复制代码
p     puintptr // 当前绑定的 P(执行 Go 代码时必须持有)
nextp puintptr // 唤醒 M 时即将绑定的 P
oldp  puintptr // 系统调用前绑定的 P(用于恢复)

3. M 状态

go 复制代码
id       int64  // M 的唯一 ID
spinning bool   // 是否正在窃取任务(Work Stealing 状态)
blocked  bool   // 是否在 note 上阻塞
locks    int32  // 持有的锁计数(用于检测死锁)
  • spinning: 关键指标!在 schedtrace 中看到的 spinningthreads 就是统计这个

4. 抢占控制

go 复制代码
preemptoff string // 非空表示禁止抢占 curg(值为原因描述)

5. CGO 相关

go 复制代码
incgo      bool   // 是否正在执行 cgo 调用
ncgocall   uint64 // 累计 cgo 调用次数
ncgo       int32  // 当前进行中的 cgo 调用数
cgoCallers *cgoCallers // cgo 崩溃时的追踪信息

6. 线程本地存储

go 复制代码
tls [tlsSlots]uintptr // Thread-Local Storage(x86 使用)

7. 系统调用支持

go 复制代码
syscalltick uint32  // 系统调用计数(用于 sysmon 检测阻塞)
libcall     libcall // 低级系统调用参数存储

8. 信号抢占(Go 1.14+)

go 复制代码
preemptGen    atomic.Uint32 // 完成的抢占信号计数
signalPending atomic.Uint32 // 是否有待处理的抢占信号

9. 链表管理

go 复制代码
alllink   *m        // 全局 M 链表(allm)
schedlink muintptr  // 调度器管理的 M 链表
freelink  *m        // 空闲 M 链表(sched.freem)

三、type p struct - 逻辑处理器结构体

1. 基本信息

go 复制代码
id     int32  // P 的 ID(0 到 GOMAXPROCS-1)
status uint32 // P 的状态(_Pidle/_Prunning/_Psyscall/_Pgcstop/_Pdead)
m      muintptr // 反向引用:当前绑定的 M
link   puintptr // 空闲 P 链表指针

2. 调度统计

go 复制代码
schedtick   uint32     // 每次调度调用递增
syscalltick uint32     // 每次系统调用递增
sysmontick  sysmontick // sysmon 最后观察的 tick 值(用于检测长时间阻塞)
  • schedtick : findRunnable 中每 61 次从全局队列取任务,就是检查这个

3. 本地运行队列 🔥 核心

go 复制代码
runqhead uint32         // 队列头索引
runqtail uint32         // 队列尾索引
runq     [256]guintptr  // 环形队列,最多 256 个 G
runnext  guintptr       // 优先级最高的下一个 G(生产者-消费者模式优化)
  • runq: 无锁访问的本地队列,Work Stealing 的来源
  • runnext: 避免调度延迟,直接运行刚创建的 goroutine

4. 对象缓存池(性能优化)

go 复制代码
deferpool    []*_defer       // defer 结构体池
gFree        struct{ ... }   // 已终止的 G 缓存(避免频繁分配)
sudogcache   []*sudog        // sudog 缓存
mspancache   struct{ ... }   // mspan 缓存
pinnerCache  *pinner         // pinner 对象缓存

5. 内存分配

go 复制代码
mcache *mcache      // 每个 P 的内存缓存(TCMalloc 风格)
pcache pageCache    // 页缓存
palloc persistentAlloc // 持久化分配器

6. Goroutine ID 生成

go 复制代码
goidcache    uint64 // 本地 ID 缓存起始值
goidcacheend uint64 // 本地 ID 缓存结束值
  • 批量从全局 sched.goidgen 获取 ID 段,减少锁竞争

7. Timer 管理 ⏰

go 复制代码
timers                []*timer       // 定时器堆
timer0When            atomic.Int64   // 堆顶定时器的触发时间
timerModifiedEarliest atomic.Int64   // 最早被修改的定时器
timersLock            mutex          // 定时器锁
numTimers             atomic.Uint32  // 定时器数量
deletedTimers         atomic.Uint32  // 已删除定时器数量
  • Go 1.14+ 每个 P 独立管理定时器,避免全局锁

8. GC 相关

go 复制代码
gcAssistTime         int64 // GC 辅助标记耗时
gcFractionalMarkTime int64 // 分数标记工作者耗时
gcMarkWorkerMode     gcMarkWorkerMode // 下一个标记工作者模式
gcw                  gcWork // GC 工作缓冲
wbBuf                wbBuf  // 写屏障缓冲

9. 抢占标志

go 复制代码
preempt bool // 标记此 P 应尽快进入调度器(不管正在运行什么 G)
  • sysmon 会设置此标志强制调度

10. 统计与调试

go 复制代码
statsSeq             atomic.Uint32 // 统计写入计数器(奇数=正在写入)
maxStackScanDelta    int64         // 累计的栈空间量
scannedStackSize     uint64        // 已扫描的栈大小
scannedStacks        uint64        // 已扫描的 goroutine 数量
runSafePointFn       uint32        // 是否在下一个安全点执行 safePointFn

四、关键关系图

perl 复制代码
用户代码执行路径:
G (goroutine) --绑定--> M (thread) --必须持有--> P (processor)
     ↓                      ↓                          ↓
  用户栈                  g0栈                    本地runq[256]
  调度上下文              调度逻辑                  mcache

五、面试高频问题

Q: 为什么需要 P? A: P 的本地队列 runq 实现无锁调度,避免所有 M 竞争全局队列。同时 P 可以在 M 阻塞时移交(Hand Off),保证并行度。

Q: runnext 的作用? A: 生产者-消费者模式优化。当前 G 创建新 G 时,新 G 放入 runnext,继承当前时间片,减少调度延迟。

Q: g0 是什么? A: 每个 M 的调度专用 goroutine,栈更大(8KB),用于执行调度器、GC、栈增长等 runtime 代码。用户代码在 curg 上运行,调度代码在 g0 上运行。

Q: spinning 的含义? A: M 处于"自旋"状态,即没有绑定 G,正在执行 Work Stealing 寻找任务。GODEBUG=schedtrace 中的 spinningthreads 就是统计这个。

这些结构体的设计体现了 Go 调度器的精髓:去中心化(本地队列)+ 工作窃取(负载均衡)+ 抢占式调度(公平性)

相关推荐
一路向北⁢2 小时前
短信登录安全防护方案(Spring Boot)
spring boot·redis·后端·安全·sms·短信登录
古城小栈2 小时前
Tokio:Rust 异步界的 “霸主”
开发语言·后端·rust
进击的丸子2 小时前
基于虹软Linux Pro SDK的多路RTSP流并发接入、解码与帧级处理实践
java·后端·github
techdashen2 小时前
Go 1.18+ slice 扩容机制详解
开发语言·后端·golang
浙江巨川-吉鹏2 小时前
【城市地表水位连续监测自动化系统】沃思智能
java·后端·struts·城市地表水位连续监测自动化系统·地表水位监测系统
fliter2 小时前
Go 1.18+ slice 扩容机制详解
后端
A黑桃2 小时前
Paimon Action Jar 实现机制分析
大数据·后端
代码笔耕2 小时前
写了几年 Java,我发现很多人其实一直在用“高级 C 语言”写代码
java·后端·架构
@我们的天空2 小时前
【FastAPI 完整版】路由与请求参数详解(query、path、params、body、form 完整梳理)- 基于 FastAPI 完整版
后端·python·pycharm·fastapi·后端开发·路由与请求