本篇文章讨论实现原理基于 Go 1.13.

在垃圾回收机制 (GC) 中,"Stop the World" (STW) 是一个重要阶段。 顾名思义, 在 "Stop the World" 阶段, 当前运行的所有程序将被暂停, 扫描内存的 root 节点和添加写屏障 (write barrier) 。 本篇文章讨论的是, "Stop the World" 内部工作原理及我们可能会遇到的潜在风险。

Stop The World(STW)

这里面的"停止", 指的是停止正在运行的 goroutines。 下面这段程序就执行了 "Stop the World":

runtime.GC()

(关于关于垃圾回收机制, 可以参考我的另外一篇文章 ):

G
P
MP

在停止了处理器和 Marking Worker 之后, 对于 Goroutine 本身, 他们会被放到一个全局队列中等待:

到目前为止, 整个"世界"被停止. 至此, 仅存的 "Stop The World" (STW)goroutine 可以开始接下来的回收工作, 在一些列的操作结束之后, 再启动整个"世界"。

我们也可以在 Tracing 工具中看到一次 STW 的运行状态:

系统调用

下面我们来讨论一下 STW 是如何处理系统调用的。

我们知道, 系统调用是需要返回的, 那么当整个"世界"被停止的时候, 已经存在的系统调用如何被处理呢?

我们通过一个实际例子来理解:

这是一段简单的系统调用的程序, 我们通过 Tracing 工具看一下它是如何被处理的:

G30
P

延迟

MP

而实际上, Go 会等待他们自发停止, 也就是说当调度器(scheduler)运行的时候, 系统调用在运行的时候, STW 会等待。

理论上, 等待一个 Goroutine 被抢占是很快的, 但是在有些情况下, 还是会出现相应的延迟。

我们通过一个例子来模拟类似情况:

我们还是来看一下这段代码运行的 Tracing 情况, 从下图我们可以看到 STW 阶段总共耗时 2.6 秒:

P

而 STW 的机制是等待它自发停止, 因此就出现了 2.6 秒的 STW。

为了提高整体程序的效率, 我们一般需要避免或者改进这种情况。

关于这部分, 大家可以参考我的另一篇文章


本文由 原创编译, 荣誉推出