golang 标准库源码解析 - runtime2 GMP
<blockquote> <p>本文源码版本基于 go1.21.13</p> <p>代码有点多,只是简单介绍下 G、M、P 每个的结构体</p> </blockquote> <h1 id="runtime2"><a class="anchor" href="#runtime2">#</a> runtime2</h1> <p>GMP 简介:<a href="https://blog.twelveeee.top/2023/Go/go%20GMP%20intro/">https://blog.twelveeee.top/2023/Go/go GMP intro/</a></p> <p>本文基于 <strong>Go 1.21.13</strong> 的 runtime2.go 源码,梳理了 Go 调度器中的 <strong>G (goroutine)、M (machine)、P (processor)</strong> 三个核心结构体,以及全局调度器 <strong>schedt</strong> 的关键字段。</p> <ul> <li><strong>G (goroutine)</strong>:是 Go 的用户态线程,封装了栈、调度上下文、panic/defer 链表、状态流转标识及 GC 相关信息,是用户代码运行的最小执行单元。</li> <li><strong>M (machine)</strong>:抽象操作系统内核线程,负责实际执行 G。它与 P 绑定,利用自己的 g0 栈来运行调度逻辑,并维护与调度、信号、cgo 调用等相关的上下文。</li> <li><strong>P (processor)</strong>:是 M 执行 Go 代码的必需上下文,维护着本地运行队列、本地内存分配缓存、定时器及 GC 写屏障等状态,它保证了 M 在运行时能高效管理 goroutine 与内存。</li> <li><strong>schedt</strong>:作为全局调度器,统一管理空闲 / 运行中的 G、M、P 队列,协调 GC、STW、安全点、以及负载均衡,保证整个 GMP 系统的正常运转。</li> </ul> <p>整体来看,Go 调度的核心思想是:<strong>G 是任务单元,M 是执行者,P 负责调度上下文和资源,schedt 做全局协调</strong>。这种 GMP 模型保证了 goroutine 调度的高并发性和轻量级,避免了对 OS 线程的过度依赖。</p> <h2 id="g"><a class="anchor" href="#g">#</a> G</h2> <p>Go runtime 里调度器 <strong>G (goroutine)</strong> 的实现,是承载用户协程的核心数据结构,记录了 goroutine 执行所需的各种上下文信息。</p> <h3 id="goroutine-基本字段"><a class="anchor" href="#goroutine-基本字段">#</a> Goroutine 基本字段</h3> <p>基本字段包括 栈与函数执行环境管理,异常处理机制(panic/defer),调度上下文保存,状态流转与调度控制,GC 协作与内存安全,运行时辅助信息 等</p> <figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> g <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token comment">// 栈相关</span></pre></td></tr><tr><td data-num="3"></td><td><pre> stack stack <span class="token comment">// 描述当前 goroutine 的栈内存区间 [stack.lo, stack.hi),lo 是栈底,hi 是栈顶。</span></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token comment">// 检查栈空间是否足够的值,低于这个值会扩张,</span></pre></td></tr><tr><td data-num="5"></td><td><pre> stackguard0 <span class="token builtin">uintptr</span> <span class="token comment">//stackguard0 供 Go 代码使用</span></pre></td></tr><tr><td data-num="6"></td><td><pre> stackguard1 <span class="token builtin">uintptr</span> <span class="token comment">//stackguard1 供 C (CGO) 代码使用</span></pre></td></tr><tr><td data-num="7"></td><td><pre></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token comment">// 指向当前 goroutine 最内层的 panic 和 defer 结构(链表)。</span></pre></td></tr><tr><td data-num="9"></td><td><pre> _panic <span class="token operator">*</span>_panic <span class="token comment">// innermost panic - offset known to liblink</span></pre></td></tr><tr><td data-num="10"></td><td><pre> _defer <span class="token operator">*</span>_defer <span class="token comment">// innermost defer</span></pre></td></tr><tr><td data-num="11"></td><td><pre></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token comment">// 调度和状态流转相关</span></pre></td></tr><tr><td data-num="13"></td><td><pre> m <span class="token operator">*</span>m <span class="token comment">// 指向当前绑定的 M</span></pre></td></tr><tr><td data-num="14"></td><td><pre> sched gobuf <span class="token comment">// 保存 goroutine 被挂起时的寄存器等调度现场(栈指针、程序计数器等)。</span></pre></td></tr><tr><td data-num="15"></td><td><pre></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token comment">// Goroutine 状态流转</span></pre></td></tr><tr><td data-num="17"></td><td><pre> <span class="token comment">// Goroutine 进入系统调用 (syscall) 时会保存栈指针和程序计数器(方便 GC 和调度器)。</span></pre></td></tr><tr><td data-num="18"></td><td><pre> syscallsp <span class="token builtin">uintptr</span> <span class="token comment">// if status==Gsyscall, syscallsp = sched.sp to use during gc</span></pre></td></tr><tr><td data-num="19"></td><td><pre> syscallpc <span class="token builtin">uintptr</span> <span class="token comment">// if status==Gsyscall, syscallpc = sched.pc to use during gc</span></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token comment">//param 一个通用指针字段,在运行时一些特殊场景用于临时参数传递,例如:</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token comment">// 1. channel 唤醒 goroutine 时传参数</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token comment">// 2. GC 辅助分配返回信号</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token comment">// 3. debugCallWrap 用于启动新协程传参</span></pre></td></tr><tr><td data-num="24"></td><td><pre> param unsafe<span class="token punctuation">.</span>Pointer</pre></td></tr><tr><td data-num="25"></td><td><pre> stktopsp <span class="token builtin">uintptr</span> <span class="token comment">//sp 位于堆栈顶部,以检查回溯</span></pre></td></tr><tr><td data-num="26"></td><td><pre> atomicstatus atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">//goroutine 状态 (_Grunnable, _Grunning, _Gwaiting, _Gsyscall 等)。</span></pre></td></tr><tr><td data-num="27"></td><td><pre> stackLock <span class="token builtin">uint32</span> <span class="token comment">// 栈扫描时的锁(GC / 性能分析用)</span></pre></td></tr><tr><td data-num="28"></td><td><pre> goid <span class="token builtin">uint64</span> <span class="token comment">//goroutine 唯一 ID(调试用,用户代码无法直接拿到)</span></pre></td></tr><tr><td data-num="29"></td><td><pre> schedlink guintptr</pre></td></tr><tr><td data-num="30"></td><td><pre> waitsince <span class="token builtin">int64</span> <span class="token comment">// G 阻塞时长</span></pre></td></tr><tr><td data-num="31"></td><td><pre> waitreason waitReason <span class="token comment">// 阻塞原因</span></pre></td></tr><tr><td data-num="32"></td><td><pre> preempt <span class="token builtin">bool</span> <span class="token comment">// 标记 goroutine 被请求抢占。</span></pre></td></tr><tr><td data-num="33"></td><td><pre> preemptStop <span class="token builtin">bool</span> <span class="token comment">// 抢占时进入 _Gpreempted 状态。</span></pre></td></tr><tr><td data-num="34"></td><td><pre> preemptShrink <span class="token builtin">bool</span> <span class="token comment">// 在同步安全点收缩</span></pre></td></tr><tr><td data-num="35"></td><td><pre></pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token comment">// GC 相关</span></pre></td></tr><tr><td data-num="37"></td><td><pre> asyncSafePoint <span class="token builtin">bool</span> <span class="token comment">// 异步安全点。如果 g 在异步安全点停止则设置为 true,表示在栈上没有精确的指针信息。</span></pre></td></tr><tr><td data-num="38"></td><td><pre> paniconfault <span class="token builtin">bool</span> <span class="token comment">// 地址异常引起的 panic(代替了崩溃)。</span></pre></td></tr><tr><td data-num="39"></td><td><pre> gcAssistBytes <span class="token builtin">int64</span> <span class="token comment">// GC 扫描字节数(在分配内存时可能被要求 “协助 GC” 扫描一定的字节数)</span></pre></td></tr><tr><td data-num="40"></td><td><pre> gcscandone <span class="token builtin">bool</span> <span class="token comment">// 该 goroutine 栈是否已被 GC 扫描。</span></pre></td></tr><tr><td data-num="41"></td><td><pre> throwsplit <span class="token builtin">bool</span> <span class="token comment">// 表明不允许拆封栈</span></pre></td></tr><tr><td data-num="42"></td><td><pre> activeStackChans <span class="token builtin">bool</span> <span class="token comment">// 表示有未锁定的通道指向此 goroutine 的堆栈。如果为真,堆栈复制需要获取通道锁来保护堆栈的这些区域。</span></pre></td></tr><tr><td data-num="43"></td><td><pre> parkingOnChan atomic<span class="token punctuation">.</span>Bool <span class="token comment">// 表示 goroutine 即将在 chansend 或 chanrecv 上 stop。用于表示堆叠收缩的不安全点。</span></pre></td></tr><tr><td data-num="44"></td><td><pre></pre></td></tr><tr><td data-num="45"></td><td><pre> <span class="token comment">// 用于 Go scheduler latency profiling(调度延迟统计)和 runtime trace。</span></pre></td></tr><tr><td data-num="46"></td><td><pre> raceignore <span class="token builtin">int8</span> <span class="token comment">// ignore race detection events</span></pre></td></tr><tr><td data-num="47"></td><td><pre> tracking <span class="token builtin">bool</span> <span class="token comment">// whether we're tracking this G for sched latency statistics</span></pre></td></tr><tr><td data-num="48"></td><td><pre> trackingSeq <span class="token builtin">uint8</span> <span class="token comment">// used to decide whether to track this G</span></pre></td></tr><tr><td data-num="49"></td><td><pre> trackingStamp <span class="token builtin">int64</span> <span class="token comment">// timestamp of when the G last started being tracked</span></pre></td></tr><tr><td data-num="50"></td><td><pre> runnableTime <span class="token builtin">int64</span> <span class="token comment">// the amount of time spent runnable, cleared when running, only used when tracking</span></pre></td></tr><tr><td data-num="51"></td><td><pre> lockedm muintptr</pre></td></tr><tr><td data-num="52"></td><td><pre></pre></td></tr><tr><td data-num="53"></td><td><pre> <span class="token comment">// 崩溃或信号处理时保存的上下文。</span></pre></td></tr><tr><td data-num="54"></td><td><pre> sig <span class="token builtin">uint32</span></pre></td></tr><tr><td data-num="55"></td><td><pre> writebuf <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">byte</span></pre></td></tr><tr><td data-num="56"></td><td><pre> sigcode0 <span class="token builtin">uintptr</span></pre></td></tr><tr><td data-num="57"></td><td><pre> sigcode1 <span class="token builtin">uintptr</span></pre></td></tr><tr><td data-num="58"></td><td><pre> sigpc <span class="token builtin">uintptr</span></pre></td></tr><tr><td data-num="59"></td><td><pre> parentGoid <span class="token builtin">uint64</span> <span class="token comment">// 父 goroutine</span></pre></td></tr><tr><td data-num="60"></td><td><pre> gopc <span class="token builtin">uintptr</span> <span class="token comment">// 创造当前 goroutine 的语句 pc 指针</span></pre></td></tr><tr><td data-num="61"></td><td><pre> ancestors <span class="token operator">*</span><span class="token punctuation">[</span><span class="token punctuation">]</span>ancestorInfo <span class="token comment">// 创建当前 goroutine 的祖先信息 goroutine</span></pre></td></tr><tr><td data-num="62"></td><td><pre> startpc <span class="token builtin">uintptr</span> <span class="token comment">// pc of goroutine function</span></pre></td></tr><tr><td data-num="63"></td><td><pre> racectx <span class="token builtin">uintptr</span></pre></td></tr><tr><td data-num="64"></td><td><pre> waiting <span class="token operator">*</span>sudog <span class="token comment">// 等待处理的 g 队列</span></pre></td></tr><tr><td data-num="65"></td><td><pre> cgoCtxt <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">uintptr</span> <span class="token comment">// cgo traceback context</span></pre></td></tr><tr><td data-num="66"></td><td><pre> labels unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// profiler labels</span></pre></td></tr><tr><td data-num="67"></td><td><pre> timer <span class="token operator">*</span>timer <span class="token comment">// 缓存 time.Sleep 所需的定时器结构,避免频繁分配。</span></pre></td></tr><tr><td data-num="68"></td><td><pre> selectDone atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// are we participating in a select and did someone win the race?</span></pre></td></tr><tr><td data-num="69"></td><td><pre></pre></td></tr><tr><td data-num="70"></td><td><pre> <span class="token comment">// goroutineProfiled indicates the status of this goroutine's stack for the</span></pre></td></tr><tr><td data-num="71"></td><td><pre> <span class="token comment">// current in-progress goroutine profile</span></pre></td></tr><tr><td data-num="72"></td><td><pre> goroutineProfiled goroutineProfileStateHolder</pre></td></tr><tr><td data-num="73"></td><td><pre></pre></td></tr><tr><td data-num="74"></td><td><pre> <span class="token comment">// Per-G tracer state.</span></pre></td></tr><tr><td data-num="75"></td><td><pre> trace gTraceState</pre></td></tr><tr><td data-num="76"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> _panic <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> argp unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">//defer 调用的参数指针</span></pre></td></tr><tr><td data-num="3"></td><td><pre> arg any <span class="token comment">//panic 的参数</span></pre></td></tr><tr><td data-num="4"></td><td><pre> link <span class="token operator">*</span>_panic <span class="token comment">// 上一层 panic(链表)</span></pre></td></tr><tr><td data-num="5"></td><td><pre> pc <span class="token builtin">uintptr</span> <span class="token comment">// 在 runtime 里恢复时使用的程序计数器</span></pre></td></tr><tr><td data-num="6"></td><td><pre> sp unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// 栈指针</span></pre></td></tr><tr><td data-num="7"></td><td><pre> recovered <span class="token builtin">bool</span> <span class="token comment">// 是否被 recover 捕获</span></pre></td></tr><tr><td data-num="8"></td><td><pre> aborted <span class="token builtin">bool</span> <span class="token comment">// 是否被 abort</span></pre></td></tr><tr><td data-num="9"></td><td><pre> goexit <span class="token builtin">bool</span> <span class="token comment">// 特殊标记:runtime.Goexit</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> _defer <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> started <span class="token builtin">bool</span> <span class="token comment">// 是否已经开始执行(防止二次执行)</span></pre></td></tr><tr><td data-num="3"></td><td><pre> heap <span class="token builtin">bool</span> <span class="token comment">// 是否分配在堆上</span></pre></td></tr><tr><td data-num="4"></td><td><pre> openDefer <span class="token builtin">bool</span> <span class="token comment">// 是否是 open-coded defer(编译器优化模式)</span></pre></td></tr><tr><td data-num="5"></td><td><pre> sp <span class="token builtin">uintptr</span> <span class="token comment">//defer 注册时的栈指针,配合栈回溯 traceback</span></pre></td></tr><tr><td data-num="6"></td><td><pre> pc <span class="token builtin">uintptr</span> <span class="token comment">// 注册 defer 位置的 PC</span></pre></td></tr><tr><td data-num="7"></td><td><pre> fn <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">//defer func 对应的函数指针</span></pre></td></tr><tr><td data-num="8"></td><td><pre> _panic <span class="token operator">*</span>_panic <span class="token comment">// 当前运行的 panic(如果这个 defer 是在 panic unwinding 阶段调用的)</span></pre></td></tr><tr><td data-num="9"></td><td><pre> link <span class="token operator">*</span>_defer <span class="token comment">// 下一层 defer,形成链表</span></pre></td></tr><tr><td data-num="10"></td><td><pre></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token comment">// 下面这些主要在 open-coded defer 模式下生效:</span></pre></td></tr><tr><td data-num="12"></td><td><pre> fd unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// 编译器生成的 funcdata,保存 defer 信息</span></pre></td></tr><tr><td data-num="13"></td><td><pre> varp <span class="token builtin">uintptr</span> <span class="token comment">// 关联的栈帧变量指针</span></pre></td></tr><tr><td data-num="14"></td><td><pre> framepc <span class="token builtin">uintptr</span> <span class="token comment">// 当前函数帧的返回 PC,用于 gentraceback ()</span></pre></td></tr><tr><td data-num="15"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">//goroutine 被挂起时的最小执行上下文</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">type</span> gobuf <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="3"></td><td><pre> sp <span class="token builtin">uintptr</span> <span class="token comment">// 栈指针寄存器,保存 goroutine 被挂起时的栈顶位置</span></pre></td></tr><tr><td data-num="4"></td><td><pre> pc <span class="token builtin">uintptr</span> <span class="token comment">// 程序计数器,保存 goroutine 被挂起时正在执行的下一条指令地址</span></pre></td></tr><tr><td data-num="5"></td><td><pre> g guintptr <span class="token comment">// 指向当前现场对应的 g 结构(goroutine 对象)</span></pre></td></tr><tr><td data-num="6"></td><td><pre> ctxt unsafe<span class="token punctuation">.</span>Pointer <span class="token comment">// 保存调度现场时的附加上下文指针。</span></pre></td></tr><tr><td data-num="7"></td><td><pre> ret <span class="token builtin">uintptr</span> <span class="token comment">// 返回地址</span></pre></td></tr><tr><td data-num="8"></td><td><pre> lr <span class="token builtin">uintptr</span></pre></td></tr><tr><td data-num="9"></td><td><pre> bp <span class="token builtin">uintptr</span> <span class="token comment">// for framepointer-enabled architectures</span></pre></td></tr><tr><td data-num="10"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h3 id="goroutine-状态流转"><a class="anchor" href="#goroutine-状态流转">#</a> Goroutine 状态流转</h3> <figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token comment">// defined constants</span></pre></td></tr><tr><td data-num="2"></td><td><pre><span class="token keyword">const</span> <span class="token punctuation">(</span></pre></td></tr><tr><td data-num="3"></td><td><pre> <span class="token comment">// G status</span></pre></td></tr><tr><td data-num="4"></td><td><pre></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token comment">//goroutine 刚被分配,还没有初始化。</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token comment">// 还没有绑定函数入口,也没有分配栈。</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token comment">// 一般在 newg 之后立即切换到 _Grunnable。</span></pre></td></tr><tr><td data-num="8"></td><td><pre> _Gidle <span class="token operator">=</span> <span class="token boolean">iota</span> <span class="token comment">// 0</span></pre></td></tr><tr><td data-num="9"></td><td><pre></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token comment">//goroutine 现在是可运行状态,在调度器的运行队列里,还没有被调度执行。</span></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token comment">// 在某个 P 的 local runq 或 global runq 里排队。</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token comment">// 栈目前不属于自己(所以 GC 扫描时可以随意移动 / 复制)。</span></pre></td></tr><tr><td data-num="13"></td><td><pre> _Grunnable <span class="token comment">// 1</span></pre></td></tr><tr><td data-num="14"></td><td><pre></pre></td></tr><tr><td data-num="15"></td><td><pre> <span class="token comment">// 当前 goroutine 正在运行,CPU 上正在执行它的用户代码。</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token comment">// 栈处于 “锁定” 状态,只能由这个 goroutine 自己使用。</span></pre></td></tr><tr><td data-num="17"></td><td><pre> _Grunning <span class="token comment">// 2</span></pre></td></tr><tr><td data-num="18"></td><td><pre></pre></td></tr><tr><td data-num="19"></td><td><pre> <span class="token comment">//goroutine 正在执行系统调用(阻塞在 syscall 上)。</span></pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token comment">// 栈依旧属于这个 goroutine。不在 run queue 上。</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token comment">// 有对应的 M,但 P 会被释放给其他 goroutines 使用。防止 syscall 长时间阻塞一个 P。</span></pre></td></tr><tr><td data-num="22"></td><td><pre> _Gsyscall <span class="token comment">// 3</span></pre></td></tr><tr><td data-num="23"></td><td><pre></pre></td></tr><tr><td data-num="24"></td><td><pre> <span class="token comment">//goroutine 正在 runtime 内部等待(比如 channel、定时器、network poll 等),阻塞状态。</span></pre></td></tr><tr><td data-num="25"></td><td><pre> <span class="token comment">// 通常被挂在某个等待队列里(chan 队列、timer 队列)</span></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token comment">// 栈不属于自己(可能会被 GC 移动)。</span></pre></td></tr><tr><td data-num="27"></td><td><pre> _Gwaiting <span class="token comment">// 4</span></pre></td></tr><tr><td data-num="28"></td><td><pre></pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token comment">// 废弃的状态,没在用。 gdb 脚本硬编码依赖它</span></pre></td></tr><tr><td data-num="30"></td><td><pre> _Gmoribund_unused <span class="token comment">// 5</span></pre></td></tr><tr><td data-num="31"></td><td><pre></pre></td></tr><tr><td data-num="32"></td><td><pre> <span class="token comment">//goroutine 已经死亡,不再使用。</span></pre></td></tr><tr><td data-num="33"></td><td><pre> <span class="token comment">// 已退出或者被清理。可能在 freelist 里复用。栈可能释放或回收。</span></pre></td></tr><tr><td data-num="34"></td><td><pre> _Gdead <span class="token comment">// 6</span></pre></td></tr><tr><td data-num="35"></td><td><pre></pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token comment">// 未使用,保留位。</span></pre></td></tr><tr><td data-num="37"></td><td><pre> _Genqueue_unused <span class="token comment">// 7</span></pre></td></tr><tr><td data-num="38"></td><td><pre></pre></td></tr><tr><td data-num="39"></td><td><pre> <span class="token comment">// 该 goroutine 的栈正在被移动(因为 Go 支持动态扩容和收缩栈)</span></pre></td></tr><tr><td data-num="40"></td><td><pre> <span class="token comment">// 不在 runq,不运行用户代码。栈处于迁移过程中。</span></pre></td></tr><tr><td data-num="41"></td><td><pre> _Gcopystack <span class="token comment">// 8</span></pre></td></tr><tr><td data-num="42"></td><td><pre></pre></td></tr><tr><td data-num="43"></td><td><pre> <span class="token comment">// 该 goroutine 被 抢占 暂停在安全点。</span></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token comment">// 类似 _Gwaiting,但还没有别的 goroutine 负责把它重新 ready。</span></pre></td></tr><tr><td data-num="45"></td><td><pre> <span class="token comment">// 用于定时触发抢占、或者 GC 辅助抢占。</span></pre></td></tr><tr><td data-num="46"></td><td><pre> <span class="token comment">// 后续需要调度器将它切换回 _Grunnable。</span></pre></td></tr><tr><td data-num="47"></td><td><pre> _Gpreempted <span class="token comment">// 9</span></pre></td></tr><tr><td data-num="48"></td><td><pre></pre></td></tr><tr><td data-num="49"></td><td><pre> <span class="token comment">// GC 相关</span></pre></td></tr><tr><td data-num="50"></td><td><pre> _Gscan <span class="token operator">=</span> <span class="token number">0x1000</span></pre></td></tr><tr><td data-num="51"></td><td><pre> _Gscanrunnable <span class="token operator">=</span> _Gscan <span class="token operator">+</span> _Grunnable <span class="token comment">// 0x1001 //goroutine 在 runq 中等待运行,但整个栈正被 GC 扫描。</span></pre></td></tr><tr><td data-num="52"></td><td><pre> _Gscanrunning <span class="token operator">=</span> _Gscan <span class="token operator">+</span> _Grunning <span class="token comment">// 0x1002 // 正在运行,但 GC 想让它协助扫描自己的栈(STW 安全点抢占)</span></pre></td></tr><tr><td data-num="53"></td><td><pre> _Gscansyscall <span class="token operator">=</span> _Gscan <span class="token operator">+</span> _Gsyscall <span class="token comment">// 0x1003 // 正在 system call,同时 GC 正在扫描它的栈</span></pre></td></tr><tr><td data-num="54"></td><td><pre> _Gscanwaiting <span class="token operator">=</span> _Gscan <span class="token operator">+</span> _Gwaiting <span class="token comment">// 0x1004 // 处于阻塞状态,且栈在被 GC 扫描。</span></pre></td></tr><tr><td data-num="55"></td><td><pre> _Gscanpreempted <span class="token operator">=</span> _Gscan <span class="token operator">+</span> _Gpreempted <span class="token comment">// 0x1009 // 处于抢占状态,同时栈在被 GC 扫描</span></pre></td></tr><tr><td data-num="56"></td><td><pre><span class="token punctuation">)</span></pre></td></tr></table></figure><p><img data-src="https://twelveeee-note.oss-cn-beijing.aliyuncs.com/Image/e59eeb98892942bba73e70ecf11893ff.png" alt="img" /></p> <h2 id="m"><a class="anchor" href="#m">#</a> M</h2> <p>m 是 Go runtime 里调度器 <strong>M (machine)</strong> 的实现,M 代表一个内核线程,实际上就是执行 goroutine 的 “物理承载者”。它和 P (Processor) 绑定,驱动 G (goroutine) 的执行。</p> <h3 id="m-基本字段"><a class="anchor" href="#m-基本字段">#</a> M 基本字段</h3> <p>基本字段包括 调度相关,执行栈与上下文保存,与操作系统交互,资源与生命周期管理,调试与监控</p> <figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> m <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> g0 <span class="token operator">*</span>g <span class="token comment">// 特殊的 goroutine,调度栈,用来执行调度相关代码而非用户代码</span></pre></td></tr><tr><td data-num="3"></td><td><pre> morebuf gobuf <span class="token comment">// 切栈时保存的上下文</span></pre></td></tr><tr><td data-num="4"></td><td><pre> divmod <span class="token builtin">uint32</span> <span class="token comment">// div/mod denominator for arm - known to liblink</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token boolean">_</span> <span class="token builtin">uint32</span> <span class="token comment">// align next field to 8 bytes</span></pre></td></tr><tr><td data-num="6"></td><td><pre></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token comment">// 调试 & 信号处理相关</span></pre></td></tr><tr><td data-num="8"></td><td><pre> procid <span class="token builtin">uint64</span> <span class="token comment">// for debuggers, but offset not hard-coded</span></pre></td></tr><tr><td data-num="9"></td><td><pre> gsignal <span class="token operator">*</span>g <span class="token comment">// 信号处理用的 G</span></pre></td></tr><tr><td data-num="10"></td><td><pre> goSigStack gsignalStack <span class="token comment">// 保存 signal stack</span></pre></td></tr><tr><td data-num="11"></td><td><pre> sigmask sigset <span class="token comment">// 信号屏蔽</span></pre></td></tr><tr><td data-num="12"></td><td><pre> tls <span class="token punctuation">[</span>tlsSlots<span class="token punctuation">]</span><span class="token builtin">uintptr</span> <span class="token comment">// thread-local storage (for x86 extern register)</span></pre></td></tr><tr><td data-num="13"></td><td><pre> mstartfn <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span></pre></td></tr><tr><td data-num="14"></td><td><pre></pre></td></tr><tr><td data-num="15"></td><td><pre> <span class="token comment">// 调度相关</span></pre></td></tr><tr><td data-num="16"></td><td><pre> curg <span class="token operator">*</span>g <span class="token comment">// 当前运行的 G</span></pre></td></tr><tr><td data-num="17"></td><td><pre> caughtsig guintptr <span class="token comment">//fatal signal 时所在的 goroutine</span></pre></td></tr><tr><td data-num="18"></td><td><pre> p puintptr <span class="token comment">// 当前 M 占有的 P (nil if not executing go code)</span></pre></td></tr><tr><td data-num="19"></td><td><pre> nextp puintptr <span class="token comment">// 下一个可绑定的 P</span></pre></td></tr><tr><td data-num="20"></td><td><pre> oldp puintptr <span class="token comment">// 调用 syscall 前绑定的 P</span></pre></td></tr><tr><td data-num="21"></td><td><pre> id <span class="token builtin">int64</span> <span class="token comment">// M 的唯一 ID</span></pre></td></tr><tr><td data-num="22"></td><td><pre> mallocing <span class="token builtin">int32</span> <span class="token comment">// 是否在执行 malloc</span></pre></td></tr><tr><td data-num="23"></td><td><pre> throwing throwType <span class="token comment">// 是否在 panic/throw</span></pre></td></tr><tr><td data-num="24"></td><td><pre> preemptoff <span class="token builtin">string</span> <span class="token comment">// 不允许被抢占的原因描述</span></pre></td></tr><tr><td data-num="25"></td><td><pre> locks <span class="token builtin">int32</span> <span class="token comment">// 持有的锁数量</span></pre></td></tr><tr><td data-num="26"></td><td><pre> dying <span class="token builtin">int32</span> <span class="token comment">// 标记此 M 是否正在退出 / 死亡</span></pre></td></tr><tr><td data-num="27"></td><td><pre> profilehz <span class="token builtin">int32</span> <span class="token comment">//profiling 采样频率</span></pre></td></tr><tr><td data-num="28"></td><td><pre> spinning <span class="token builtin">bool</span> <span class="token comment">// M 是否在空转找 G</span></pre></td></tr><tr><td data-num="29"></td><td><pre> blocked <span class="token builtin">bool</span> <span class="token comment">// M 是否阻塞</span></pre></td></tr><tr><td data-num="30"></td><td><pre> newSigstack <span class="token builtin">bool</span> <span class="token comment">// minit on C thread called sigaltstack</span></pre></td></tr><tr><td data-num="31"></td><td><pre> printlock <span class="token builtin">int8</span></pre></td></tr><tr><td data-num="32"></td><td><pre></pre></td></tr><tr><td data-num="33"></td><td><pre> <span class="token comment">//cgo & 系统调用相关</span></pre></td></tr><tr><td data-num="34"></td><td><pre> incgo <span class="token builtin">bool</span> <span class="token comment">//m 是否正在执行 cgo 调用</span></pre></td></tr><tr><td data-num="35"></td><td><pre> isextra <span class="token builtin">bool</span> <span class="token comment">//m 是一个额外的 m</span></pre></td></tr><tr><td data-num="36"></td><td><pre> isExtraInC <span class="token builtin">bool</span> <span class="token comment">//m 是一个额外的 m,它没有执行 Go 代码</span></pre></td></tr><tr><td data-num="37"></td><td><pre> freeWait atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// 释放 g0 并删除 m(freeMRef、freeMStack、freeMWait 之一)是否安全</span></pre></td></tr><tr><td data-num="38"></td><td><pre> fastrand <span class="token builtin">uint64</span></pre></td></tr><tr><td data-num="39"></td><td><pre> needextram <span class="token builtin">bool</span></pre></td></tr><tr><td data-num="40"></td><td><pre> traceback <span class="token builtin">uint8</span></pre></td></tr><tr><td data-num="41"></td><td><pre> ncgocall <span class="token builtin">uint64</span> <span class="token comment">// 累计 cgo 调用数</span></pre></td></tr><tr><td data-num="42"></td><td><pre> ncgo <span class="token builtin">int32</span> <span class="token comment">// 当前进行的 cgo 调用数</span></pre></td></tr><tr><td data-num="43"></td><td><pre> cgoCallersUse atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// if non-zero, cgoCallers in use temporarily</span></pre></td></tr><tr><td data-num="44"></td><td><pre> cgoCallers <span class="token operator">*</span>cgoCallers <span class="token comment">// cgo traceback if crashing in cgo call</span></pre></td></tr><tr><td data-num="45"></td><td><pre> park note</pre></td></tr><tr><td data-num="46"></td><td><pre></pre></td></tr><tr><td data-num="47"></td><td><pre> <span class="token comment">// 链接管理</span></pre></td></tr><tr><td data-num="48"></td><td><pre> alllink <span class="token operator">*</span>m <span class="token comment">// 全局 allm 链表</span></pre></td></tr><tr><td data-num="49"></td><td><pre> freelink <span class="token operator">*</span>m <span class="token comment">// 空闲资源链表</span></pre></td></tr><tr><td data-num="50"></td><td><pre> schedlink muintptr <span class="token comment">// 调度器链表,用于管理调度队列</span></pre></td></tr><tr><td data-num="51"></td><td><pre> lockedg guintptr</pre></td></tr><tr><td data-num="52"></td><td><pre> createstack <span class="token punctuation">[</span><span class="token number">32</span><span class="token punctuation">]</span><span class="token builtin">uintptr</span> <span class="token comment">// 创建此线程的栈</span></pre></td></tr><tr><td data-num="53"></td><td><pre> lockedExt <span class="token builtin">uint32</span> <span class="token comment">// tracking for external LockOSThread</span></pre></td></tr><tr><td data-num="54"></td><td><pre> lockedInt <span class="token builtin">uint32</span> <span class="token comment">// tracking for internal lockOSThread</span></pre></td></tr><tr><td data-num="55"></td><td><pre> nextwaitm muintptr <span class="token comment">// 下一个等待锁的 m</span></pre></td></tr><tr><td data-num="56"></td><td><pre></pre></td></tr><tr><td data-num="57"></td><td><pre> <span class="token comment">//wait* 用于将参数从 gopark 携带到 park_m 中,因为没有栈可以放置它们。这是它们的唯一目的。</span></pre></td></tr><tr><td data-num="58"></td><td><pre> waitunlockf <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token operator">*</span>g<span class="token punctuation">,</span> unsafe<span class="token punctuation">.</span>Pointer<span class="token punctuation">)</span> <span class="token builtin">bool</span></pre></td></tr><tr><td data-num="59"></td><td><pre> waitlock unsafe<span class="token punctuation">.</span>Pointer</pre></td></tr><tr><td data-num="60"></td><td><pre> waitTraceBlockReason traceBlockReason</pre></td></tr><tr><td data-num="61"></td><td><pre> waitTraceSkip <span class="token builtin">int</span></pre></td></tr><tr><td data-num="62"></td><td><pre></pre></td></tr><tr><td data-num="63"></td><td><pre> syscalltick <span class="token builtin">uint32</span></pre></td></tr><tr><td data-num="64"></td><td><pre> trace mTraceState</pre></td></tr><tr><td data-num="65"></td><td><pre></pre></td></tr><tr><td data-num="66"></td><td><pre> <span class="token comment">// 这些是因为它们太大,无法放在低级 NOSPLIT 函数的堆栈上。</span></pre></td></tr><tr><td data-num="67"></td><td><pre> libcall libcall</pre></td></tr><tr><td data-num="68"></td><td><pre> libcallpc <span class="token builtin">uintptr</span> <span class="token comment">// for cpu profiler</span></pre></td></tr><tr><td data-num="69"></td><td><pre> libcallsp <span class="token builtin">uintptr</span></pre></td></tr><tr><td data-num="70"></td><td><pre> libcallg guintptr</pre></td></tr><tr><td data-num="71"></td><td><pre> syscall libcall <span class="token comment">// stores syscall parameters on windows</span></pre></td></tr><tr><td data-num="72"></td><td><pre></pre></td></tr><tr><td data-num="73"></td><td><pre> vdsoSP <span class="token builtin">uintptr</span> <span class="token comment">// SP for traceback while in VDSO call (0 if not in call)</span></pre></td></tr><tr><td data-num="74"></td><td><pre> vdsoPC <span class="token builtin">uintptr</span> <span class="token comment">// PC for traceback while in VDSO call</span></pre></td></tr><tr><td data-num="75"></td><td><pre></pre></td></tr><tr><td data-num="76"></td><td><pre> <span class="token comment">// preemptGen counts the number of completed preemption</span></pre></td></tr><tr><td data-num="77"></td><td><pre> <span class="token comment">// signals. This is used to detect when a preemption is</span></pre></td></tr><tr><td data-num="78"></td><td><pre> <span class="token comment">// requested, but fails.</span></pre></td></tr><tr><td data-num="79"></td><td><pre> preemptGen atomic<span class="token punctuation">.</span>Uint32</pre></td></tr><tr><td data-num="80"></td><td><pre></pre></td></tr><tr><td data-num="81"></td><td><pre> <span class="token comment">// Whether this is a pending preemption signal on this M.</span></pre></td></tr><tr><td data-num="82"></td><td><pre> signalPending atomic<span class="token punctuation">.</span>Uint32</pre></td></tr><tr><td data-num="83"></td><td><pre></pre></td></tr><tr><td data-num="84"></td><td><pre> dlogPerM</pre></td></tr><tr><td data-num="85"></td><td><pre></pre></td></tr><tr><td data-num="86"></td><td><pre> mOS <span class="token comment">// 平台相关的 OS 线程数据(在不同平台里定义)</span></pre></td></tr><tr><td data-num="87"></td><td><pre></pre></td></tr><tr><td data-num="88"></td><td><pre> <span class="token comment">// Up to 10 locks held by this m, maintained by the lock ranking code.</span></pre></td></tr><tr><td data-num="89"></td><td><pre> locksHeldLen <span class="token builtin">int</span></pre></td></tr><tr><td data-num="90"></td><td><pre> locksHeld <span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span>heldLockInfo</pre></td></tr><tr><td data-num="91"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h2 id="p"><a class="anchor" href="#p">#</a> P</h2> <p>P 是 Go runtime 里调度器 <strong>P (processor)</strong> 的实现,P 是协程调度的关键角色,它维护着与调度、内存分配、垃圾回收、定时器等多个方面相关的本地状态,可以看作是 <strong>"M 执行 G 的上下文环境"</strong>。</p> <h3 id="p基本字段"><a class="anchor" href="#p基本字段">#</a> P 基本字段</h3> <figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> p <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> id <span class="token builtin">int32</span></pre></td></tr><tr><td data-num="3"></td><td><pre> status <span class="token builtin">uint32</span> <span class="token comment">// 状态</span></pre></td></tr><tr><td data-num="4"></td><td><pre> link puintptr <span class="token comment">// 空闲 P 链表用的指针</span></pre></td></tr><tr><td data-num="5"></td><td><pre> m muintptr <span class="token comment">// 当前 P 绑定的 M (如果空闲则为 nil)</span></pre></td></tr><tr><td data-num="6"></td><td><pre> raceprocctx <span class="token builtin">uintptr</span> <span class="token comment">// 用于数据竞争检测</span></pre></td></tr><tr><td data-num="7"></td><td><pre></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token comment">// 缓存 goroutine id,分摊对 runtime・sched.goiden 的访问。</span></pre></td></tr><tr><td data-num="9"></td><td><pre> goidcache <span class="token builtin">uint64</span></pre></td></tr><tr><td data-num="10"></td><td><pre> goidcacheend <span class="token builtin">uint64</span></pre></td></tr><tr><td data-num="11"></td><td><pre></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token comment">// 调度相关</span></pre></td></tr><tr><td data-num="13"></td><td><pre> schedtick <span class="token builtin">uint32</span> <span class="token comment">// 每一个 scheduler call 自增</span></pre></td></tr><tr><td data-num="14"></td><td><pre> syscalltick <span class="token builtin">uint32</span> <span class="token comment">// 每一 system call 自增</span></pre></td></tr><tr><td data-num="15"></td><td><pre> sysmontick sysmontick <span class="token comment">//sysmon 观察到的最后一个 tick,sysmon 会周期性检查调度状态(如长时间 GC、长时间 syscall 等),如果 P 没有活动会触发抢占调度。</span></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token comment">//p 可运行的 G 本地队列。无需锁即可访问。</span></pre></td></tr><tr><td data-num="17"></td><td><pre> runqhead <span class="token builtin">uint32</span></pre></td></tr><tr><td data-num="18"></td><td><pre> runqtail <span class="token builtin">uint32</span></pre></td></tr><tr><td data-num="19"></td><td><pre> runq <span class="token punctuation">[</span><span class="token number">256</span><span class="token punctuation">]</span>guintptr</pre></td></tr><tr><td data-num="20"></td><td><pre> <span class="token comment">// 如果 runnext 非空,则表示当前 G 已将其准备好,并且接下来应该运行该 G,而不是 runq 中的 G。</span></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token comment">// 如果当前 G 的时间片还有剩余时间,则 runnext 将继承当前时间片的剩余时间。</span></pre></td></tr><tr><td data-num="22"></td><td><pre> runnext guintptr <span class="token comment">// 一个快速路径优化,如果某 G 是由当前正在运行的 G 唤醒的,可以直接放在 runnext 位置,它会优先运行(减少调度延迟)。</span></pre></td></tr><tr><td data-num="23"></td><td><pre></pre></td></tr><tr><td data-num="24"></td><td><pre> <span class="token comment">// 内存分配 & 缓存</span></pre></td></tr><tr><td data-num="25"></td><td><pre> mcache <span class="token operator">*</span>mcache <span class="token comment">// 本地缓存,小对象分配都会先在本地 mcache 中完成,这是 Go 内存分配性能高的关键</span></pre></td></tr><tr><td data-num="26"></td><td><pre> pcache pageCache <span class="token comment">// 大页分配的缓存</span></pre></td></tr><tr><td data-num="27"></td><td><pre> <span class="token comment">// 缓存来自堆的 mspan ,加速 span 分配</span></pre></td></tr><tr><td data-num="28"></td><td><pre> mspancache <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="29"></td><td><pre> <span class="token builtin">len</span> <span class="token builtin">int</span></pre></td></tr><tr><td data-num="30"></td><td><pre> buf <span class="token punctuation">[</span><span class="token number">128</span><span class="token punctuation">]</span><span class="token operator">*</span>mspan</pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="32"></td><td><pre> deferpool <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">*</span>_defer <span class="token comment">// 缓存 _defer 对象,复用避免频繁分配。</span></pre></td></tr><tr><td data-num="33"></td><td><pre> deferpoolbuf <span class="token punctuation">[</span><span class="token number">32</span><span class="token punctuation">]</span><span class="token operator">*</span>_defer</pre></td></tr><tr><td data-num="34"></td><td><pre> sudogcache <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">*</span>sudog <span class="token comment">// 缓存 sudog</span></pre></td></tr><tr><td data-num="35"></td><td><pre> sudogbuf <span class="token punctuation">[</span><span class="token number">128</span><span class="token punctuation">]</span><span class="token operator">*</span>sudog</pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token comment">// 减少 pinner 分配次数</span></pre></td></tr><tr><td data-num="37"></td><td><pre> pinnerCache <span class="token operator">*</span>pinner</pre></td></tr><tr><td data-num="38"></td><td><pre> <span class="token comment">// 可用的 G's (status == Gdead) 可以复用已经死亡的 G 对象,避免频繁 malloc</span></pre></td></tr><tr><td data-num="39"></td><td><pre> gFree <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="40"></td><td><pre> gList</pre></td></tr><tr><td data-num="41"></td><td><pre> n <span class="token builtin">int32</span></pre></td></tr><tr><td data-num="42"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="43"></td><td><pre></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token comment">// 定时器相关</span></pre></td></tr><tr><td data-num="45"></td><td><pre> timer0When atomic<span class="token punctuation">.</span>Int64 <span class="token comment">// 堆顶 timer 的到期时间</span></pre></td></tr><tr><td data-num="46"></td><td><pre> timersLock mutex <span class="token comment">// 访问 timers 时需要加锁</span></pre></td></tr><tr><td data-num="47"></td><td><pre> timers <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">*</span>timer <span class="token comment">// 在某个时候要采取的行动。</span></pre></td></tr><tr><td data-num="48"></td><td><pre> timerModifiedEarliest atomic<span class="token punctuation">.</span>Int64</pre></td></tr><tr><td data-num="49"></td><td><pre> numTimers atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// P 的堆中的计时器数量。</span></pre></td></tr><tr><td data-num="50"></td><td><pre> deletedTimers atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// P 的堆中的 timerDeleted 计时器的数量。</span></pre></td></tr><tr><td data-num="51"></td><td><pre> timerRaceCtx <span class="token builtin">uintptr</span> <span class="token comment">// 执行计时器功能时使用的 Race context。</span></pre></td></tr><tr><td data-num="52"></td><td><pre></pre></td></tr><tr><td data-num="53"></td><td><pre> <span class="token comment">// 垃圾回收相关</span></pre></td></tr><tr><td data-num="54"></td><td><pre> gcAssistTime <span class="token builtin">int64</span> <span class="token comment">//assistAlloc 的 Nanoseconds 数</span></pre></td></tr><tr><td data-num="55"></td><td><pre> gcFractionalMarkTime <span class="token builtin">int64</span> <span class="token comment">//fractional mark worker (atomic) 的 Nanoseconds 数</span></pre></td></tr><tr><td data-num="56"></td><td><pre> <span class="token comment">// 有关当前 goroutines 的 gc-time 统计数据</span></pre></td></tr><tr><td data-num="57"></td><td><pre> scannedStackSize <span class="token builtin">uint64</span> <span class="token comment">// 此 P 扫描的 goroutine 的堆栈大小</span></pre></td></tr><tr><td data-num="58"></td><td><pre> scannedStacks <span class="token builtin">uint64</span> <span class="token comment">// 此 P 扫描的 goroutine 数量</span></pre></td></tr><tr><td data-num="59"></td><td><pre> maxStackScanDelta <span class="token builtin">int64</span> <span class="token comment">// 累计当前活跃 Goroutine(即有资格进行堆栈扫描的 Goroutine)所占用的堆栈空间大小。</span></pre></td></tr><tr><td data-num="60"></td><td><pre> limiterEvent limiterEvent <span class="token comment">// 跟踪 GC CPU 限制器的事件。</span></pre></td></tr><tr><td data-num="61"></td><td><pre> <span class="token comment">//gcMarkWorkerMode 是下一个 mark worker 的运行模式。也就是说,它用于与 gcController.findRunnableGCWorker 选中并立即执行的工作线程进行通信。</span></pre></td></tr><tr><td data-num="62"></td><td><pre> <span class="token comment">// 在调度其他工作线程时,必须将此字段设置为 gcMarkWorkerNotWorker。</span></pre></td></tr><tr><td data-num="63"></td><td><pre> gcMarkWorkerMode gcMarkWorkerMode</pre></td></tr><tr><td data-num="64"></td><td><pre> gcMarkWorkerStartTime <span class="token builtin">int64</span> <span class="token comment">// 是最近一个 mark worker 启动时的 nanotime ()。</span></pre></td></tr><tr><td data-num="65"></td><td><pre> gcw gcWork <span class="token comment">//gcw 是此 P 的 GC 工作缓冲区,用于存放当前被扫描出来的对象。在 STW 和 GC 调度间切换时要特别处理。</span></pre></td></tr><tr><td data-num="66"></td><td><pre> wbBuf wbBuf <span class="token comment">//wbBuf 是这个 P 的 GC 写入屏障缓冲区。</span></pre></td></tr><tr><td data-num="67"></td><td><pre></pre></td></tr><tr><td data-num="68"></td><td><pre> <span class="token comment">// 其他</span></pre></td></tr><tr><td data-num="69"></td><td><pre> runSafePointFn <span class="token builtin">uint32</span> <span class="token comment">// 如果为 1,则在下一个安全点运行 sched.safePointFn</span></pre></td></tr><tr><td data-num="70"></td><td><pre> trace pTraceState <span class="token comment">//runtime trace 相关</span></pre></td></tr><tr><td data-num="71"></td><td><pre> palloc persistentAlloc <span class="token comment">//per-P 以避免互斥,每个 P 有自己的单独 allocator,减少全局锁争用</span></pre></td></tr><tr><td data-num="72"></td><td><pre> statsSeq atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// 判断是否在写 stats</span></pre></td></tr><tr><td data-num="73"></td><td><pre> preempt <span class="token builtin">bool</span> <span class="token comment">// 如果为 true,表明当前 goroutine 将被尽快抢占</span></pre></td></tr><tr><td data-num="74"></td><td><pre> pageTraceBuf pageTraceBuf <span class="token comment">//pageTraceBuf 用于记录页分配 / 释放的 trace 日志,只有开启 GOEXPERIMENT=pagetrace 时才会用到</span></pre></td></tr><tr><td data-num="75"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure><h3 id="p状态流转"><a class="anchor" href="#p状态流转">#</a> P 状态流转</h3> <figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">const</span> <span class="token punctuation">(</span></pre></td></tr><tr><td data-num="2"></td><td><pre> <span class="token comment">// P status</span></pre></td></tr><tr><td data-num="3"></td><td><pre></pre></td></tr><tr><td data-num="4"></td><td><pre> <span class="token comment">// P 处于空闲状态,没有正在执行用户代码或调度逻辑。</span></pre></td></tr><tr><td data-num="5"></td><td><pre> <span class="token comment">// 可以被新的 M 获取来运行 G;</span></pre></td></tr><tr><td data-num="6"></td><td><pre> <span class="token comment">//run queue(本地运行队列)为空;</span></pre></td></tr><tr><td data-num="7"></td><td><pre> <span class="token comment">// 与任何 M 绑定。</span></pre></td></tr><tr><td data-num="8"></td><td><pre> _Pidle <span class="token operator">=</span> <span class="token boolean">iota</span></pre></td></tr><tr><td data-num="9"></td><td><pre></pre></td></tr><tr><td data-num="10"></td><td><pre> <span class="token comment">// P 正在与某个 M 绑定,并用于运行用户代码或调度器逻辑。</span></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token comment">// 该状态下,P 属于某个 M;</span></pre></td></tr><tr><td data-num="12"></td><td><pre> <span class="token comment">// 执行用户 Goroutine 或调度任务;</span></pre></td></tr><tr><td data-num="13"></td><td><pre> <span class="token comment">// 只有拥有该 P 的 M 可以改变它的状态;</span></pre></td></tr><tr><td data-num="14"></td><td><pre> _Prunning</pre></td></tr><tr><td data-num="15"></td><td><pre></pre></td></tr><tr><td data-num="16"></td><td><pre> <span class="token comment">// P 与正在执行系统调用的 M 有亲和性 (affinity),但并不直接绑定。</span></pre></td></tr><tr><td data-num="17"></td><td><pre> <span class="token comment">// 在系统调用阻塞时间过长时,P 可以被其他 M 偷走,以避免调度停滞;</span></pre></td></tr><tr><td data-num="18"></td><td><pre> <span class="token comment">// 因为存在 CAS(Compare-And-Swap)操作竞争,可能出现 ABA 问题(即 P 在被 CAS 拿回之前,可能已被其他 M 使用过)。</span></pre></td></tr><tr><td data-num="19"></td><td><pre> _Psyscall</pre></td></tr><tr><td data-num="20"></td><td><pre></pre></td></tr><tr><td data-num="21"></td><td><pre> <span class="token comment">// P 被 GC STW 时挂起。</span></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token comment">// 属于发起 STW 的 M</span></pre></td></tr><tr><td data-num="23"></td><td><pre> <span class="token comment">// 依旧保留自身的 run queue</span></pre></td></tr><tr><td data-num="24"></td><td><pre> _Pgcstop</pre></td></tr><tr><td data-num="25"></td><td><pre></pre></td></tr><tr><td data-num="26"></td><td><pre> <span class="token comment">// 因为 GOMAXPROCS 减小了,不再使用的 P</span></pre></td></tr><tr><td data-num="27"></td><td><pre> <span class="token comment">// 被认为 “死亡”,资源基本被清理;</span></pre></td></tr><tr><td data-num="28"></td><td><pre> <span class="token comment">// 如果后续 GOMAXPROCS 增大,可以复用;</span></pre></td></tr><tr><td data-num="29"></td><td><pre> _Pdead</pre></td></tr><tr><td data-num="30"></td><td><pre><span class="token punctuation">)</span></pre></td></tr></table></figure><p><img data-src="https://twelveeee-note.oss-cn-beijing.aliyuncs.com/Image/f669935df35a6598aa05ecdfae9bee4e.png" alt="img" /></p> <h2 id="schedt"><a class="anchor" href="#schedt">#</a> schedt</h2> <p>schedt 是 全局唯一的调度器实例,保存了 Go 调度器运行时需要的核心全局状态,比如 Goroutine 的运行队列、空闲 M/P 的缓存、统计信息、定时器、安全点、垃圾回收(GC)的协调状态等等。</p> <figure class="highlight go"><figcaption data-lang="go"></figcaption><table><tr><td data-num="1"></td><td><pre><span class="token keyword">type</span> schedt <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="2"></td><td><pre> goidgen atomic<span class="token punctuation">.</span>Uint64 <span class="token comment">//goroutine ID 的生成器,全局自增 ID 分配来源</span></pre></td></tr><tr><td data-num="3"></td><td><pre> lastpoll atomic<span class="token punctuation">.</span>Int64 <span class="token comment">// 最近一次网络轮询的时间戳</span></pre></td></tr><tr><td data-num="4"></td><td><pre> pollUntil atomic<span class="token punctuation">.</span>Int64 <span class="token comment">// 当前 M 在 network poll 中会阻塞到的时间</span></pre></td></tr><tr><td data-num="5"></td><td><pre></pre></td></tr><tr><td data-num="6"></td><td><pre> lock mutex</pre></td></tr><tr><td data-num="7"></td><td><pre></pre></td></tr><tr><td data-num="8"></td><td><pre> <span class="token comment">// When increasing nmidle, nmidlelocked, nmsys, or nmfreed, be</span></pre></td></tr><tr><td data-num="9"></td><td><pre> <span class="token comment">// sure to call checkdead().</span></pre></td></tr><tr><td data-num="10"></td><td><pre></pre></td></tr><tr><td data-num="11"></td><td><pre> <span class="token comment">// M 管理</span></pre></td></tr><tr><td data-num="12"></td><td><pre> midle muintptr <span class="token comment">// 空闲的 M 链表</span></pre></td></tr><tr><td data-num="13"></td><td><pre> nmidle <span class="token builtin">int32</span> <span class="token comment">// 空闲的 M 数量</span></pre></td></tr><tr><td data-num="14"></td><td><pre> nmidlelocked <span class="token builtin">int32</span> <span class="token comment">// 被锁定(只能运行特定 goroutine)的空闲 M</span></pre></td></tr><tr><td data-num="15"></td><td><pre> mnext <span class="token builtin">int64</span> <span class="token comment">// 下一个 M 的 ID 分配器</span></pre></td></tr><tr><td data-num="16"></td><td><pre> maxmcount <span class="token builtin">int32</span> <span class="token comment">// 允许创建的最大 M 数量(防止无限创建线程)</span></pre></td></tr><tr><td data-num="17"></td><td><pre> nmsys <span class="token builtin">int32</span> <span class="token comment">// 特殊系统 M 数量(不计入死锁检查,比如 GC / 获取跟踪用的 sysmon M)</span></pre></td></tr><tr><td data-num="18"></td><td><pre> nmfreed <span class="token builtin">int64</span> <span class="token comment">// 已被释放的 M 累计数。</span></pre></td></tr><tr><td data-num="19"></td><td><pre></pre></td></tr><tr><td data-num="20"></td><td><pre> ngsys atomic<span class="token punctuation">.</span>Int32 <span class="token comment">// number of system goroutines</span></pre></td></tr><tr><td data-num="21"></td><td><pre></pre></td></tr><tr><td data-num="22"></td><td><pre> <span class="token comment">// P 管理</span></pre></td></tr><tr><td data-num="23"></td><td><pre> pidle puintptr <span class="token comment">// 空闲的 P 链表</span></pre></td></tr><tr><td data-num="24"></td><td><pre> npidle atomic<span class="token punctuation">.</span>Int32 <span class="token comment">// 空闲的 P 数量</span></pre></td></tr><tr><td data-num="25"></td><td><pre> nmspinning atomic<span class="token punctuation">.</span>Int32 <span class="token comment">// 当前处于自旋状态的 M 数(忙等以尝试获取 G)。</span></pre></td></tr><tr><td data-num="26"></td><td><pre> needspinning atomic<span class="token punctuation">.</span>Uint32 <span class="token comment">// 调度器是否需要更多自旋线程(细节见 proc.go 的 “Delicate dance” 注释)。</span></pre></td></tr><tr><td data-num="27"></td><td><pre></pre></td></tr><tr><td data-num="28"></td><td><pre> runq gQueue <span class="token comment">// 全局运行 G 队列</span></pre></td></tr><tr><td data-num="29"></td><td><pre> runqsize <span class="token builtin">int32</span> <span class="token comment">// 全局运行 G 队列大小</span></pre></td></tr><tr><td data-num="30"></td><td><pre></pre></td></tr><tr><td data-num="31"></td><td><pre> <span class="token comment">// 可控开关,能选择是否禁止用户 Goroutine 的调度(例如 runtime 某些特殊场景)</span></pre></td></tr><tr><td data-num="32"></td><td><pre> disable <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="33"></td><td><pre> user <span class="token builtin">bool</span></pre></td></tr><tr><td data-num="34"></td><td><pre> runnable gQueue <span class="token comment">// 等待运行的 G 队列</span></pre></td></tr><tr><td data-num="35"></td><td><pre> n <span class="token builtin">int32</span> <span class="token comment">// 等待运行的 G 队列的长度</span></pre></td></tr><tr><td data-num="36"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="37"></td><td><pre></pre></td></tr><tr><td data-num="38"></td><td><pre> <span class="token comment">// 全局缓存回收的 G 对象</span></pre></td></tr><tr><td data-num="39"></td><td><pre> gFree <span class="token keyword">struct</span> <span class="token punctuation">{</span></pre></td></tr><tr><td data-num="40"></td><td><pre> lock mutex</pre></td></tr><tr><td data-num="41"></td><td><pre> stack gList <span class="token comment">// 有栈的 G 队列</span></pre></td></tr><tr><td data-num="42"></td><td><pre> noStack gList <span class="token comment">// 没有栈的 G 队列</span></pre></td></tr><tr><td data-num="43"></td><td><pre> n <span class="token builtin">int32</span></pre></td></tr><tr><td data-num="44"></td><td><pre> <span class="token punctuation">}</span></pre></td></tr><tr><td data-num="45"></td><td><pre></pre></td></tr><tr><td data-num="46"></td><td><pre> <span class="token comment">//sudog 对象的缓存和锁</span></pre></td></tr><tr><td data-num="47"></td><td><pre> sudoglock mutex</pre></td></tr><tr><td data-num="48"></td><td><pre> sudogcache <span class="token operator">*</span>sudog</pre></td></tr><tr><td data-num="49"></td><td><pre></pre></td></tr><tr><td data-num="50"></td><td><pre> <span class="token comment">// 可用的 defer pool 和锁</span></pre></td></tr><tr><td data-num="51"></td><td><pre> deferlock mutex</pre></td></tr><tr><td data-num="52"></td><td><pre> deferpool <span class="token operator">*</span>_defer</pre></td></tr><tr><td data-num="53"></td><td><pre></pre></td></tr><tr><td data-num="54"></td><td><pre> <span class="token comment">//freem 是等待在 m.exited 被设置时被释放的 m 的列表。通过 m.freelink 链接。</span></pre></td></tr><tr><td data-num="55"></td><td><pre> freem <span class="token operator">*</span>m</pre></td></tr><tr><td data-num="56"></td><td><pre></pre></td></tr><tr><td data-num="57"></td><td><pre> gcwaiting atomic<span class="token punctuation">.</span>Bool <span class="token comment">// GC 是否在等待运行</span></pre></td></tr><tr><td data-num="58"></td><td><pre> stopwait <span class="token builtin">int32</span> <span class="token comment">// STW 时的同步</span></pre></td></tr><tr><td data-num="59"></td><td><pre> stopnote note <span class="token comment">// STW 时的同步</span></pre></td></tr><tr><td data-num="60"></td><td><pre> sysmonwait atomic<span class="token punctuation">.</span>Bool <span class="token comment">//sysmon 相关状态</span></pre></td></tr><tr><td data-num="61"></td><td><pre> sysmonnote note</pre></td></tr><tr><td data-num="62"></td><td><pre></pre></td></tr><tr><td data-num="63"></td><td><pre> <span class="token comment">// 用于 GC 的安全点机制</span></pre></td></tr><tr><td data-num="64"></td><td><pre> safePointFn <span class="token keyword">func</span><span class="token punctuation">(</span><span class="token operator">*</span>p<span class="token punctuation">)</span></pre></td></tr><tr><td data-num="65"></td><td><pre> safePointWait <span class="token builtin">int32</span></pre></td></tr><tr><td data-num="66"></td><td><pre> safePointNote note</pre></td></tr><tr><td data-num="67"></td><td><pre></pre></td></tr><tr><td data-num="68"></td><td><pre> profilehz <span class="token builtin">int32</span> <span class="token comment">// CPU profiling 的采样频率。</span></pre></td></tr><tr><td data-num="69"></td><td><pre></pre></td></tr><tr><td data-num="70"></td><td><pre> procresizetime <span class="token builtin">int64</span> <span class="token comment">// 最后更新 gomaxprocs 的 nanotime ()</span></pre></td></tr><tr><td data-num="71"></td><td><pre> totaltime <span class="token builtin">int64</span> <span class="token comment">// ∫gomaxprocs dt up to procresizetime</span></pre></td></tr><tr><td data-num="72"></td><td><pre></pre></td></tr><tr><td data-num="73"></td><td><pre> <span class="token comment">// sysmonlock protects sysmon's actions on the runtime.</span></pre></td></tr><tr><td data-num="74"></td><td><pre> <span class="token comment">//</span></pre></td></tr><tr><td data-num="75"></td><td><pre> <span class="token comment">// Acquire and hold this mutex to block sysmon from interacting</span></pre></td></tr><tr><td data-num="76"></td><td><pre> <span class="token comment">// with the rest of the runtime.</span></pre></td></tr><tr><td data-num="77"></td><td><pre> sysmonlock mutex</pre></td></tr><tr><td data-num="78"></td><td><pre></pre></td></tr><tr><td data-num="79"></td><td><pre> timeToRun timeHistogram <span class="token comment">// 统计 Goroutine 从 _Grunnable 到 _Grunning 的延迟分布</span></pre></td></tr><tr><td data-num="80"></td><td><pre> idleTime atomic<span class="token punctuation">.</span>Int64 <span class="token comment">// 统计 P 的累计空闲时间(在 GC 周期内会清零)。</span></pre></td></tr><tr><td data-num="81"></td><td><pre></pre></td></tr><tr><td data-num="82"></td><td><pre> totalMutexWaitTime atomic<span class="token punctuation">.</span>Int64 <span class="token comment">// Goroutine 在 sync.Mutex 等锁上等待的总时间</span></pre></td></tr><tr><td data-num="83"></td><td><pre><span class="token punctuation">}</span></pre></td></tr></table></figure>