1. 主要组成部分
Go语言的GMP调度器基于四个核心数据结构:g
、m
、p
和schedt
。
1.1 主要常量解读
1.1.1G 状态常量
const (
_Gidle = iota //刚分配尚未初始化的 G
_Grunnable//已在运行队列上,未执行用户代码;栈未被该 G 拥有
_Grunning//正在执行用户代码;已分配 M 与 P;不在运行队列上;栈由该 G 拥有
_Gsyscall//正在执行系统调用;不执行用户代码;不在运行队列上;已分配 M;栈由该 G 拥有
_Gwaiting//在 runtime 内部阻塞(如通道、锁、定时器等);不执行用户代码;不在运行队列上;通常不拥有栈(特定通道路径在锁下可读写)
_Gmoribund_unused//预留给调试器(gdb)
_Gdead//当前未使用的 G(刚退出、在空闲链表、或初始化中);不执行用户代码;可能有或没有栈;由处理其回收/复用的 M 暂时拥有
_Genqueue_unused
_Gcopystack//正在进行栈移动/拷贝;不执行用户代码;不在运行队列上;栈由发起拷贝的一方拥有
_Gpreempted//因 suspendG 抢占而自停,类似 _Gwaiting,但尚无人负责将其 ready;需要某个 suspendG 将状态 CAS 为 _Gwaiting 并负责唤醒
//GC 扫描期叠加位,用于标注 goroutine 栈扫描/自扫描期间的状态,除了_Gscanrunning
_Gscan = 0x1000
_Gscanrunnable = _Gscan + _Grunnable
_Gscanrunning = _Gscan + _Grunning
_Gscansyscall = _Gscan + _Gsyscall
_Gscanwaiting = _Gscan + _Gwaiting
_Gscanpreempted = _Gscan + _Gpreempted
)
1.1.2 P的状态常量
const (
// P status
_Pidle = iota // 空闲:未用于运行用户代码或调度器,通常在空闲队列
_Prunning // 运行中:被某个 M 持有,用于运行用户代码或调度器
_Psyscall // 系统调用关联:不运行用户代码,与系统调用中的 M 有亲和性,可被其他 M 窃取
_Pgcstop // GC 停止:STW 期间暂停,由发起 STW 的 M 持有
_Pdead // 已死亡:GOMAXPROCS 变小后不再使用,资源基本被剥离
)
1.2 重要数据结构
1.2.1 gobuf的结构体
// 源码位置:go/src/runtime/runtime2.go
type gobuf struct {
// sp, pc, g的偏移量是已知的(硬编码在)libmach中
//
// ctxt在GC方面是不寻常的:它可能是堆分配的funcval,
// 所以GC需要跟踪它,但它需要从汇编中设置和清除,
// 在那里很难有写屏障。然而,ctxt实际上是一个保存的活动寄存器,
// 我们只在真实寄存器和gobuf之间交换它。
// 因此,我们在栈扫描期间将其视为根,这意味着保存和恢复它的汇编不需要写屏障
// 它仍然被类型化为指针,以便Go的任何其他写入都获得写屏障
sp uintptr // 栈指针
pc uintptr // 程序计数器
g guintptr // goroutine指针(绕过写屏障)
ctxt unsafe.Pointer // 上下文指针,通常指向funcval
lr uintptr // 链接寄存器(用于某些架构)
bp uintptr // 基指针(用于启用帧指针的架构)
//ret uintptr // 1.24.4被删除 返回值 现在直接通过寄存器或 goroutine 的栈传递返回值
}
主要作用:
gobuf是运行时"协程上下文快照"的结构;调度、栈切换、cgo 边界、栈扩容等一切需要"暂停-继续"的地方,都会写/读它;真正的保存和跳转由 gosave/gogo 这类汇编原语完成。
1.2.2 G的结构体
type g struct {
// Stack parameters.
// stack 描述实际栈内存区间: [stack.lo, stack.hi)。
// stackguard0 是 Go 栈增长前序中比较的栈指针。
// 通常为 stack.lo+StackGuard,但可设为 StackPreempt 以触发抢占。
// stackguard1 是 //go:systemstack 栈增长前序中比较的栈指针。
// 在 g0 和 gsignal 栈上为 stack.lo+StackGuard;
// 在其他 goroutine 栈上为 ~0,以触发 morestackc 并导致崩溃。
stack stack // 偏移量由 runtime/cgo 已知
stackguard0 uintptr // 偏移量由 liblink 已知
stackguard1 uintptr // 偏移量由 liblink 已知
_panic *_panic // 最内层 panic,偏移量由 liblink 已知
_defer *_defer // 最内层 defer
m *m // 当前绑定的 M,偏移量由 arm liblink 已知
sched gobuf // 调度上下文(保存寄存器、PC、SP 等)
// 系统调用相关上下文(用于 GC 时保留)
syscallsp uintptr // 若 status==Gsyscall,此处存 sched.sp
syscallpc uintptr // 若 status==Gsyscall,此处存 sched.pc
syscallbp uintptr // 若 status==Gsyscall,此处存 sched.bp 用于回溯
stktopsp uintptr // 回溯时期望的栈顶 SP
// param 是通用指针参数字段,用于在特定场景传递值:
// 1. channel 操作唤醒阻塞 goroutine 时,指向该次阻塞的 sudog;
// 2. gcAssistAlloc1 通知调用者完成 GC 周期(因栈可能已搬迁);
// 3. debugCallWrap 传参给新 goroutine(运行时禁止闭包分配);
// 4. panic 恢复返回到某帧时,指向 savedOpenDeferState。
param unsafe.Pointer
atomicstatus atomic.Uint32 // 原子状态
stackLock uint32 // sigprof/scang 锁;TODO: 合并到 atomicstatus
goid uint64 // goroutine 唯一 ID
schedlink guintptr // 全局可运行队列链接
waitsince int64 // 估计阻塞开始时间
waitreason waitReason // 若 status==Gwaiting,记录原因
// 抢占控制
preempt bool // 抢占信号(等价于 stackguard0 = stackpreempt)
preemptStop bool // 抢占时转入 _Gpreempted,否则仅去调度
preemptShrink bool // 在同步安全点收缩栈
// asyncSafePoint 表示 g 在异步安全点停下,此时栈上可能无精确指针信息
asyncSafePoint bool
// paniconfault 在意外故障地址时 panic 而非直接 crash
paniconfault bool
// gcscandone 表示栈扫描已完成,受 status 的 _Gscan 位保护
gcscandone bool
// throwsplit 禁止在此 g 上进行栈分裂
throwsplit bool
// activeStackChans 表示未加锁 channel 引用此栈,收缩时需加锁保护
activeStackChans bool
// parkingOnChan 表示即将 park 在 chansend/chanrecv 上,标记栈收缩不安全点
parkingOnChan atomic.Bool
// inMarkAssist 表示是否在执行 mark assist(执行跟踪使用)
inMarkAssist bool
coroexit bool // coroswitch_m 的参数
// raceignore 忽略竞态检测事件
raceignore int8 // 忽略竞态检测事件
nocgocallback bool // 禁止从 C 回调到 Go
tracking bool // 是否跟踪此 G 的调度延迟统计
trackingSeq uint8 // 决定是否跟踪的序列号
trackingStamp int64 // 开始跟踪的时间戳
runnableTime int64 // 可运行时间累计(运行时清零),仅在跟踪时使用
lockedm muintptr // LockThread 时锁定的 M
fipsIndicator uint8 // FIPS 模式指示
syncSafePoint bool // 是否停在同步安全点
runningCleanups atomic.Bool // 是否正在运行清理函数
sig uint32 // 信号编号
writebuf byte // 信号处理写缓冲区
sigcode0 uintptr // 信号处理相关寄存器
sigcode1 uintptr
sigpc uintptr
parentGoid uint64 // 创建此 goroutine 的父 goid
gopc uintptr // 创建此 goroutine 的 go 语句的 PC
ancestors *ancestorInfo // debug.tracebackancestors 模式下的祖先链
startpc uintptr // goroutine 函数入口 PC
racectx uintptr // 竞态检测上下文
// waiting 指向当前 g 等待的 sudog 链表(elem 有效)
waiting *sudog
cgoCtxtuintptr // cgo 回溯上下文
labels unsafe.Pointer // 性能分析器标签
timer *timer // time.Sleep 缓存的定时器
sleepWhen int64 // 睡眠到期时间
selectDone atomic.Uint32 // 是否参与 select 及是否赢得唤醒竞赛
// goroutineProfiled 表示当前 goroutine 栈在 profile 中的状态
goroutineProfiled goroutineProfileStateHolder
coroarg *coro // 协程切换参数
bubble *synctestBubble // 同步测试气泡
// Per-G 追踪状态
trace gTraceState
// GC 相关状态
// gcAssistBytes 为 GC 辅助信用额度(字节数):
// >0 表示有可用额度;
// <0 表示需完成扫描工作。
// 通过 assist ratio 转换为扫描工作债务。
gcAssistBytes int64
// valgrindStackID 在 valgrind build tag 下用于跟踪栈内存,否则未使用
valgrindStackID uintptr
}
G的状态转换图

1.2.3 M的结构体
type m struct {
g0 *g// g0: 持有调度堆栈的goroutine
morebuf gobuf// morebuf: 传递给morestack的gobuf参数
divmod uint32// divmod: ARM平台的除法/模运算分母(已知liblink,详见cmd/internal/obj/arm/obj5.go)
// 调试器未知的字段
procid uint64 // 供调试器使用,但偏移量未硬编码
gsignal *g // 信号处理专用的goroutine
goSigStack gsignalStack // Go分配的信号处理堆栈
sigmask sigset // 保存的信号掩码存储
tls [tlsSlots]uintptr // 线程局部存储(用于x86外部寄存器)
mstartfn func() // M启动函数
curg *g // 当前运行的goroutine
caughtsig guintptr // 在致命信号期间运行的goroutine
p puintptr // 附加的P(用于执行Go代码,未执行Go代码时为nil)
nextp puintptr // 下一个P
oldp puintptr // 执行系统调用前附加的P
id int64 // M的唯一ID
mallocing int32 // 是否在分配内存
throwing throwType // 当前抛出类型
preemptoff string // 若非空,强制保持curg在此M上运行
locks int32 // 保持锁定的次数
dying int32 // 死亡状态标志
profilehz int32 // 性能分析频率
spinning bool // M处于空闲状态并主动寻找工作
blocked bool // M被note阻塞
newSigstack bool // minit在C线程中调用了sigaltstack
printlock int8 // 打印锁
incgo bool // 是否在执行cgo调用
isextra bool // 是否为备用M
isExtraInC bool // 是否为在C代码中运行的备用M
isExtraInSig bool // 是否为在信号处理中运行的备用M
freeWait atomic.Uint32 // 是否可以安全释放g0并删除M(freeMRef/freeMStack/freeMWait之一)
needextram bool // 是否需要备用M
g0StackAccurate bool // g0堆栈是否具有准确边界
traceback uint8 // 回溯类型
allpSnapshot []*p // 附加P时的allp快照(findRunnable释放P后使用,否则为nil)
ncgocall uint64 // 总cgo调用次数
ncgo int32 // 当前进行的cgo调用次数
cgoCallersUse atomic.Uint32 // 若非零,cgoCallers临时可用
cgoCallers *cgoCallers // cgo调用崩溃时的回溯信息
park note // 用于阻塞M的note
alllink *m // 在allm链表中的链接
schedlink muintptr // 调度链表链接
lockedg guintptr // 锁定的goroutine
createstack [32]uintptr // 创建此线程的堆栈(用于StackRecord.Stack0,必须对齐)
lockedExt uint32 // 外部LockOSThread状态追踪
lockedInt uint32 // 内部lockOSThread状态追踪
mWaitList mWaitList // 运行时锁等待者列表
mLockProfile mLockProfile // 与runtime.lock争用相关的字段
profStackuintptr // 用于内存/阻塞/互斥锁堆栈追踪
// wait*字段用于从gopark传递参数到park_m(因为低级NOSPLIT函数没有堆栈)
waitunlockf func(*g, unsafe.Pointer) bool // 解锁函数
waitlock unsafe.Pointer // 等待的锁
waitTraceSkip int // 跟踪跳过次数
waitTraceBlockReason traceBlockReason // 跟踪阻塞原因
syscalltick uint32 // 系统调用计数
freelink *m // 在sched.freem链表中的链接
trace mTraceState // 跟踪状态
// 这些字段太大,不能放在低级NOSPLIT函数的堆栈中
libcall libcall // 系统调用参数
libcallpc uintptr // 用于CPU性能分析
libcallsp uintptr // 调用堆栈指针
libcallg guintptr // 当前goroutine
winsyscall winlibcall // Windows平台的系统调用参数
vdsoSP uintptr // VDSO调用中的堆栈指针(未调用时为0)
vdsoPC uintptr // VDSO调用中的程序计数器
// preemptGen: 记录已完成的抢占信号次数(用于检测抢占失败)
preemptGen atomic.Uint32
// 该M是否有待处理的抢占信号
signalPending atomic.Uint32
// pcvalue查找缓存
pcvalueCache pcvalueCache
dlogPerM // 每M的日志记录
mOS // 操作系统相关字段
chacha8 chacha8rand.State // ChaCha8随机数生成器状态
cheaprand uint64 // 快速随机数生成器
// 该M持有的最多10把锁(由锁排序代码维护)
locksHeldLen int // 持有锁数量
locksHeld [10]heldLockInfo // 持有锁信息数组
}
M的内存大小
const mRedZoneSize = (16 << 3) * asanenabledBit // redZoneSize(2048)
type mPadded struct {
m
// Size the runtime.m structure so it fits in the 2048-byte size class, and
// not in the next-smallest (1792-byte) size class. That leaves the 11 low
// bits of muintptr values available for flags, as required by
// lock_spinbit.go.
_ [(1 - goarch.IsWasm) * (2048 - mallocHeaderSize - mRedZoneSize - unsafe.Sizeof(m{}))]byte
}
mRedZoneSize是在启用Ascan时,作为栈溢出检测的区域,大小为128字节。也用于栈扩展部分,当stackstackguard0进入red zone,会触发gorwStack扩展栈。
mPadded
的设计目的是 通过填充字段 确保 m
结构体大小为 2048 字节,从而在 muintptr
中 保留低 11 位用于标志位 。该设计在非 Wasm 平台生效,Wasm 平台因内存模型差异跳过填充。核心优化点在于 标志位复用 和 内存对齐,避免额外内存分配,提升并发性能。
1.2.3 P的结构体
type p struct {
id int32 // 进程ID
status uint32 // 状态(如 pidle/prunning/...)
link puintptr // 链表指针
schedtick uint32 // 每次调度调用时递增的计数器
syscalltick uint32 // 每次系统调用时递增的计数器
sysmontick sysmontick // sysmon 上次观察到的计数器
m muintptr // 关联的 M(空闲时为 nil)
mcache *mcache // 本地 M 的缓存
pcache pageCache // 页缓存
raceprocctx uintptr // 竞态检测上下文
deferpool []*_defer // 可用 defer 结构体池(见 panic.go)
deferpoolbuf [32]*_defer // defer 结构体缓冲区
// 缓存的 goroutine ID,减少对 runtime·sched.goidgen 的访问
goidcache uint64 // goroutine ID 缓存起始值
goidcacheend uint64 // 缓存的 goroutine ID 终止值
// 可运行的 goroutine 队列(无锁访问)
runqhead uint32 // 队列头部索引
runqtail uint32 // 队列尾部索引
runq [256]guintptr // 队列数组
// runnext 存储当前 G 准备运行的下一个 G(若时间片未用尽)。
// 它继承当前时间片的剩余时间。若一组 goroutine 处于通信和等待模式中,
// 此字段可将该组作为一个单元调度,避免将 goroutine 添加到队列尾部导致的调度延迟。
// 注意:其他 P 可以原子地将此字段置为零,但只有当前 P 可以原子地设置为有效 G。
runnext guintptr
// 可用的 G(状态 == Gdead)
gFree gList // 可回收的 G 列表
sudogcache []*sudog // sudog 缓存
sudogbuf [128]*sudog // sudog 缓冲区
// 从堆中缓存的 mspan 对象
mspancache struct {
// 显式长度字段,避免在分配路径中使用写屏障时的复杂性
len int // 当前缓存长度
buf [128]*mspan // 缓存数组
}
// 缓存的单个 pinner 对象,减少重复创建 pinner 的分配开销
pinnerCache *pinner
trace pTraceState // 跟踪状态
palloc persistentAlloc // 每个 P 的持久化分配器
// 每个 P 的 GC 状态
gcAssistTime int64 // GC 辅助分配所花费的时间(纳秒)
gcFractionalMarkTime int64 // 分数标记工作者所花费的时间(纳秒,原子更新)
// limiterEvent 跟踪 GC CPU 限制器的事件
limiterEvent limiterEvent
// gcMarkWorkerMode 指示下一个标记工作者应运行的模式
// 用于与通过 gcController.findRunnableGCWorker 选择的工作者 goroutine 通信
// 调度其他 goroutine 时,此字段必须设置为 gcMarkWorkerNotWorker
gcMarkWorkerMode gcMarkWorkerMode
// gcMarkWorkerStartTime 是最近标记工作者的启动时间(纳秒)
gcMarkWorkerStartTime int64
// gcw 是此 P 的 GC 工作缓冲区缓存
// 缓冲区由写屏障填充,由突变器辅助消耗,在特定 GC 状态转换时释放
gcw gcWork
// wbBuf 是此 P 的 GC 写屏障缓冲区
// TODO: 考虑将其缓存在运行的 G 中
wbBuf wbBuf
runSafePointFn uint32 // 若为 1,调度器在下一个安全点运行 sched.safePointFn
// statsSeq 是指示此 P 是否正在写入统计信息的计数器
// 偶数表示未写入,奇数表示正在写入
statsSeq atomic.Uint32
// 定时器堆
timers timers
// 清理块
cleanups *cleanupBlock
cleanupsQueued uint64 // 此 P 队列的清理块数量(单调递增)
// maxStackScanDelta 累积活动 goroutine 的栈空间占用(即可能需要扫描的栈大小)
// 当达到 maxStackScanSlack 或 -maxStackScanSlack 时,刷新到 gcController.maxStackScan
maxStackScanDelta int64
// GC 时间统计
// 与 maxStackScan 不同,该字段累积 GC 时实际观察到的栈使用量(hi - sp)
// 而非瞬时的总栈大小(hi - lo)
scannedStackSize uint64 // 此 P 扫描的 goroutine 栈大小
scannedStacks uint64 // 此 P 扫描的 goroutine 数量
// preempt 标记此 P 需尽快进入调度器(无论当前 G 在运行什么)
preempt bool
// gcStopTime 是此 P 最近进入 _Pgcstop 的时间戳(纳秒)
gcStopTime int64
}
1.2.4 schedt结构体
type schedt struct {
// goidgen 是全局唯一的 goroutine ID 生成器
// lastpoll 记录上次网络轮询的时间(若当前正在轮询则为 0)
// pollUntil 记录当前轮询的睡眠截止时间
// pollingNet 表示是否有 P 正在执行非阻塞的网络轮询
goidgen atomic.Uint64
lastpoll atomic.Int64 // 上次网络轮询时间
pollUntil atomic.Int64 // 当前轮询的睡眠截止时间
pollingNet atomic.Int32 // 1 表示有 P 正在执行非阻塞轮询
// lock 是全局调度器的互斥锁
lock mutex
// 增加 nmidle、nmidlelocked、nmsys 或 nmfreed 时,必须调用 checkdead()
// midle 是等待工作的空闲 M 链表
// nmidle 是空闲 M 的数量
// nmidlelocked 是被锁定的空闲 M 数量
// mnext 是已创建的 M 总数和下一个 M 的 ID
// maxmcount 是允许的最大 M 数量(超过则终止)
// nmsys 是系统 M 的数量(不计入死锁检测)
// nmfreed 是累计释放的 M 数量
midle muintptr // 空闲 M 链表
nmidle int32 // 空闲 M 数量
nmidlelocked int32 // 被锁定的空闲 M 数量
mnext int64 // 已创建的 M 数量和下一个 M ID
maxmcount int32 // 最大允许的 M 数量
nmsys int32 // 系统 M 数量
nmfreed int64 // 累计释放的 M 数量
// ngsys 是系统 goroutine 的数量
ngsys atomic.Int32
// pidle 是空闲 P 链表
// npidle 是空闲 P 数量(原子更新)
// nmspinning 是自旋的 M 数量(参考 proc.go 中的"Worker thread parking/unparking"注释)
// needspinning 是是否需要自旋的标志(参考 proc.go 中的"Delicate dance"注释,布尔值,修改时需持有 sched.lock)
pidle puintptr // 空闲 P 链表
npidle atomic.Int32
nmspinning atomic.Int32
needspinning atomic.Uint32
// 全局可运行队列
runq gQueue
// disable 控制调度器的禁用(通过 schedEnableUser 控制)
// disable.user 表示是否禁用用户 goroutine 的调度
// disable.runnable 是待运行的 G 队列
disable struct {
user bool
runnable gQueue // 待运行的 G 队列
}
// gFree 是全局死亡 G 的缓存
gFree struct {
lock mutex
stack gList // 带栈的 G 列表
noStack gList // 无栈的 G 列表
}
// sudoglock 保护 sudogcache 的互斥锁
// sudogcache 是全局 sudog 缓存
sudoglock mutex
sudogcache *sudog
// deferlock 保护 deferpool 的互斥锁
// deferpool 是全局 defer 结构体缓存
deferlock mutex
deferpool *_defer
// freem 是 m.exited 被设置后等待释放的 M 链表(通过 m.freelink 连接)
freem *m
// gcwaiting 表示 GC 是否在等待运行
// stopwait 和 stopnote 用于 stop-the-world 等待
// sysmonwait 表示 sysmon 是否在等待
// sysmonnote 是 sysmon 的等待信号
gcwaiting atomic.Bool // GC 等待运行标志
stopwait int32
stopnote note
sysmonwait atomic.Bool
sysmonnote note
// safePointFn 是在下一个 GC 安全点需要调用的函数(若 p.runSafePointFn 被设置)
// safePointWait 是等待计数
// safePointNote 是安全点的等待信号
safePointFn func(*p)
safePointWait int32
safePointNote note
// profilehz 是 CPU 采样率
profilehz int32
// procresizetime 是上次调整 gomaxprocs 的时间(纳秒)
// totaltime 是从 procresizetime 开始的累计运行时间
procresizetime int64 // 上次调整 gomaxprocs 的时间
totaltime int64 // 累计运行时间
// customGOMAXPROCS 表示 GOMAXPROCS 是否被手动设置(环境变量或 runtime.GOMAXPROCS)
customGOMAXPROCS bool
// sysmonlock 是保护 sysmon 与运行时交互的互斥锁
// 持有该锁可阻断 sysmon 对运行时的操作
sysmonlock mutex
// timeToRun 是调度延迟分布(定义为 G 在 _Grunnable 状态到 _Grunning 状态的总时间)
timeToRun timeHistogram
// idleTime 是所有 P 的空闲时间总和(每次 GC 周期重置)
idleTime atomic.Int64
// totalMutexWaitTime 是 goroutine 在 _Gwaiting 状态等待 runtime 内部锁的总时间
totalMutexWaitTime atomic.Int64
// stwStoppingTimeGC/Other 是 stop-the-world 停止延迟分布(定义为 stopTheWorldWithSema 到所有 P 停止的时间)
// stwStoppingTimeGC 覆盖所有 GC 相关的 STW,stwStoppingTimeOther 覆盖其他 STW
stwStoppingTimeGC timeHistogram
stwStoppingTimeOther timeHistogram
// stwTotalTimeGC/Other 是 stop-the-world 总延迟分布(定义为 stopTheWorldWithSema 到 startTheWorldWithSema 的总时间)
// stwTotalTimeGC 覆盖所有 GC 相关的 STW,stwTotalTimeOther 覆盖其他 STW
stwTotalTimeGC timeHistogram
stwTotalTimeOther timeHistogram
// totalRuntimeLockWaitTime(加上每个 M 的 lockWaitTime)是 goroutine 在 _Grunnable 状态且持有 M 但等待 runtime 内部锁的总时间
// 该字段存储已退出 M 的累计时间
totalRuntimeLockWaitTime atomic.Int64
}
剩余结构体作用
type libcall struct {
fn uintptr
n uintptr // 参数个数
args uintptr // 参数列表
r1 uintptr // 返回值1
r2 uintptr // 返回值2
err uintptr // 错误号
}
// Stack 描述了 Go 运行时的执行栈。
// 栈的边界恰好是 [lo, hi),
// 两端没有任何隐式的数据结构。
type stack struct {
lo uintptr
hi uintptr
}
// heldLockInfo 提供已持有的锁及该锁等级的信息
type heldLockInfo struct {
lockAddr uintptr
rank lockRank
}
其中libcall在汇编/系统层面,runtime 会构造一个 libcall,把要调用的函数地址、参数列表打包到它的各字段里,然后由通用的调用入口(如 asm/syscall 实现)读取这些字段并真正发起调用,返回值和 errno 也写回到这里。
每个 G 对象里有一个 stack 字段,用 lo、hi 精确标识它的栈内存区域(lo ≤ sp < hi)。GC、栈扩展/收缩、调度等子系统都依赖这两个边界来判断栈是否需要 grow/shrink,以及扫描活跃帧时的地址合法性
Go 运行时为了在调试模式下检测可能的死锁或锁顺序反转,会给每把锁分配一个 rank。每当 G 获取一把锁,就往它的 heldLocks 列表里插入一个 heldLockInfo。释放时再删掉。这样就能在运行时断言"只允许按 rank 升序获取锁",及时报告不安全的锁顺序。
参考文献