Skip to content
All notes

运行时

归纳 Go 调度器、内存和运行时机制的面试知识点。

运行时

GMP 调度

角色含义
Ggoroutine,待调度的任务
M操作系统线程,实际执行代码
P执行 Go 代码所需的调度资源,带有本地运行队列等状态

M 执行普通 Go 代码前需要绑定 P。P 的本地队列存放可运行的 G。引入 P,可以分散全局队列的竞争,改善局部性,并让空闲 P 从其他 P 获取工作。

常见调度路径:

  • 新建 G 通常进入当前 P 的本地待运行结构。
  • 本地队列满时,把部分 G 转移到全局队列。
  • 本地无工作时,从全局队列取任务,或从其他 P 偷取工作(work stealing);网络轮询也可能提供可运行的 G。
  • 调度器会定期检查全局队列以保证公平,不能把调度顺序背成固定的“本地 → 全局 → 偷取”。

image.png

GC

Go GC 的基本模型是并发标记—清扫:从根集合遍历可达对象,回收未标记的对象。业务 goroutine 在大部分标记、清扫阶段可以继续运行。

三色标记

颜色是对标记状态的抽象:

颜色含义
白尚未发现;标记结束后仍为白色的对象可回收
灰已发现,但其指针字段尚未扫描完
黑自身及其指针字段已扫描完

根集合包括全局变量、goroutine 栈中的指针,以及运行时持有的堆引用。从根发现对象并标灰,扫描灰对象时把它引用的白对象标灰,再把自身标黑,直到标记工作完成。

image.png

一个 GC 周期

  1. Sweep termination(STW):暂停业务执行,必要时完成上一轮残余清扫,为标记做准备。
  2. Mark(并发):启用写屏障,扫描根和堆中的可达对象;业务 goroutine 同时运行,必要时协助标记。
  3. Mark termination(STW):确认标记完成,进行状态同步和收尾。
  4. Sweep(并发):回收未标记对象,清扫也可能随内存分配按需进行。

所以“并发 GC”仍有 STW,主要出现在标记前后的两个阶段。

混合写屏障

并发标记时,业务代码会修改指针关系。例如把尚未发现的对象从未扫描区域移到已扫描对象下,可能让 GC 漏掉它。写屏障在指针写入时补充标记,防止可达对象被隐藏。

Go 混合写屏障结合删除屏障与插入屏障。源码给出的原理伪代码是:

writePointer(slot, ptr):
    shade(*slot)
    if current stack is grey:
        shade(ptr)
    *slot = ptr
  • 标记旧引用,防止它从堆中被移除后,只藏在尚未扫描的栈上。
  • 必要时标记新引用,防止白对象从栈移入已扫描的堆对象。
  • 栈扫描完成后保持黑色,避免在标记结束时再做一次大规模 STW 栈重扫。

这是解释屏障正确性的模型,实际实现还涉及批量处理和写屏障缓冲;不能据此认为所有指针写入都执行同一段检查。参见 混合写屏障源码。

参考