Golang Go语言中的Runtime: WriteBarrier

发布于 1周前 作者 eggper 来自 Go语言

在阅读 Go runtime 相关的代码时, 我们可以看到大量 write barrier 相关的代码或者注释, 大概可以猜到是和 GC 相关的, 具体用途和原理之前确并不知晓.

Go 的垃圾回收可以被简化为两个步骤: 标记(mark)和清除(sweep).

  • 标记阶段, GC 将所有存活的对象标记为黑色.
  • 清除阶段, GC 遍历内存将所有未被标记的对象回收.

GC 在标记阶段并不暂停所有协程的执行. 这就需要我们考虑, 如果对象的引用关系在标记阶段被修改了应该怎么办?

假设:

  • t1 时, 对象 A 被标记为黑色, 即 A 引用的对象已都被 GC 发现并处理.
  • t2 时, 用户新建了对象 C, 并将其赋值给 A 的字段 ref, 即 A.ref = C

由于 C 仅被 A 引用, 但 A 已被标记为黑色, 所以 GC 不会再去标记 C. 在随后的清除阶段, 虽然 C 依然被引用, 但是会因为未被标记而被 GC 的回收, 这显然时不可接受的.

为了处理这种情况, Go 引入 write barrier, 即由编译器在需要的地方插入相关代码处理.

我们构造一个相关的例子:

var sink *int

func main() { foo := []int{1, 2, 3} sink = &foo[1] }

在生成的汇编代码中我们可以找到相关内容:

cat -n objdump | grep -A 100 "main.main>:"
...
138038          sink = &foo[1]
138039    4576b1:       48 8d 48 08             lea    0x8(%rax),%rcx
138040    4576b5:       83 3d d4 10 0a 00 00    cmpl   $0x0,0xa10d4(%rip)        # 4f8790 <runtime.writeBarrier>
138041    4576bc:       74 15                   je     4576d3 <main.main+0x53>
138042    4576be:       66 90                   xchg   %ax,%ax
138043    4576c0:       e8 1b d2 ff ff          call   4548e0 <runtime.gcWriteBarrier2>
138044    4576c5:       49 89 0b                mov    %rcx,(%r11)
138045    4576c8:       48 8b 05 51 32 07 00    mov    0x73251(%rip),%rax        # 4ca920 <main.sink>
138046    4576cf:       49 89 43 08             mov    %rax,0x8(%r11)
138047    4576d3:       48 89 0d 46 32 07 00    mov    %rcx,0x73246(%rip)        # 4ca920 <main.sink>
...

其逻辑是:

  • 通过全局变量 runtime.writeBarrier 判断是否开启了 write barrier

    runtime.writeBarrier 是一个全局变量, 在进入标记段前开启, 进入清除阶段前关闭.

  • 如果开启了, 则调用 runtime.gcWriteBarrier2 将对象保存到当前的 p, GMP 中的 p

    runtime.gcWriteBarrier2 是直接以会编实现的函数, 会将寄存器 AX 内的指针保存到 p.wbbuf.

  • 标记阶段结束时, GC 会额外处理这些对象

    在结束标记前, GC 会为调用 wbBufFlush 处理这缓存的对象.

Link: https://github.com/j2gg0s/j2gg0s/blob/main/_posts/2023-11-01-Go%20Runtime%3A%20WriteBarrier.md


Golang Go语言中的Runtime: WriteBarrier

更多关于Golang Go语言中的Runtime: WriteBarrier的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang Go语言中的Runtime: WriteBarrier的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang(Go语言)中,Runtime: WriteBarrier 是一个与垃圾回收(GC)相关的机制,旨在处理在并发环境下对内存的安全写操作。Go语言的垃圾回收器是并发的,这意味着它试图在应用程序运行的同时回收不再使用的内存。然而,这种并发性带来了复杂性,特别是在处理指针写操作时。

WriteBarrier 的主要作用是确保在GC期间,对指针字段的写操作能够正确地更新GC的内部数据结构,同时避免数据竞争和内存泄漏。具体来说,它会在必要时暂停或重定向写操作,以确保GC能够正确地跟踪和回收内存。

在Go的GC实现中,写屏障分为几种类型,其中最常见的是“Dijkstra写屏障”和“Yuasa写屏障”的变种。这些写屏障通过不同的策略来平衡性能和正确性。例如,Dijkstra写屏障侧重于在写操作时立即更新GC状态,而Yuasa写屏障则可能延迟更新,以减少对程序运行的影响。

总的来说,WriteBarrier 是Go语言GC机制中不可或缺的一部分,它使得Go能够在保持高性能的同时,提供安全的内存管理。开发者通常不需要直接与写屏障交互,因为Go的运行时会自动处理这些细节。然而,理解写屏障的存在和工作原理,有助于更深入地理解Go的内存管理和并发模型。

回到顶部