《Go 语言原本》

8.6 标记终止阶段

终止检测算法

标记终止阶段的移除

在 Go 1.5 之前, Go 的垃圾回收器是一个全局的垃圾回收器,Go 持续支持 STW 垃圾回收作为调试模式。 Go 1.5 引入了并发收集器,但为了最少限度的减少大规模侵入式更改,它将大部分现有的 GC 机制保留为 STW 的标记终止 (Mark Termination), 同时在 STW 阶段之前添加并发标记阶段。尽管这个并发标记阶段尽可能的实现了理应并发的部分,但最终还是需要 STW 算法来清理它没有实现的工作。

在 Go 1.8 之前,并发标记总是留下至少一部分工作(Dijkstra 写屏障的特点),因为在标记终止期间,必须重新扫描并发阶段期间修改的任何栈。 Go 1.8 引入了一个新的写屏障(即混合写屏障),不再需要对栈进行重新扫描,从而大大减少了标记终止时必须完成的工作量。 然而,由于它之前从未真正的重要,我们对于进入标记终止阶段是相当马虎的:决定何时进入标记终止的算法通常需要等到所有工作都由并发标记完成, 但有时部分工作会一楼,留下标记终止清理混乱。

此外,为了最小化(但不消除)过早进入标记终止的机会,Go 1.5 将并行标记划分为 Mark1 和 Mark2 两个阶段。在 Mark1 期间, 当它用完全局标记工作时,将刷新并禁用所有本地工作缓存(启用立即标黑模式)并输入 Mark2。在 Mark2 期间,当它再次用完全局标记工作时, 它将进入标记终止。不幸的是,立即标黑的模式会对性能造成一定影响(这些本地缓存存在原因),并且算法可以在 GC 周期的早起进入 Mark2, 因为它仅检测工作瓶颈。虽然禁用所有本地缓存旨在防止标记过早终止,但这并不总是有效。

使用无竞争算法替换 Mark2

证明

定理: 当算法终止时,如果所有标记工作队列为空,则终止检测算法会成功。