写屏障是什么_Golang 混合写屏障原理深入剖析,这篇文章给你梳理的明明白白!!!...

760e39c3555467c69a890d87a3e2d7fa.gif

f13796eb133b9a7892be769925980935.png

来源 |奇伢云存储

责编 |  晋兆雨

图片来源 | CSDN 下载自视觉中国

f45d54f38c515eb93d16a857efe24afe.png

大纲

  • 插入写屏障

  • 删除写屏障

  • 混合写屏障

  • golang 混合写屏障实现

    • 源码文件

    • 写屏障代码触发点

    • 对象置灰

    • 暂停 goroutine,栈扫描

  • 总结

网上很多文章对 golang 实现的插入写屏障说的挺详细,但是删除写屏障原理和混合写屏障的由来语焉不详,这篇文章目标是弄明白混合写屏障到底是怎么来的,解决什么问题,插入写屏障,删除写屏障的特点分别是什么?

首先明白一个前提,栈上,寄存器对象的赋值(插入,删除)不能 hook ,这个因为实力不允许(太复杂,做不到)。

dd2bedba50ea1848174a78cc7a4afbc0.png

插入写屏障

Dijkstra屏障

伪代码:

writePointer(slot, ptr):
    shade(ptr)
    *slot = ptr

总结:

  1. 如果是纯粹的插入写屏障是满足强三色不变式的(永远不会出现黑色对象指向白色对象);
  2. 但是由于栈上对象无写屏障(不 hook),那么导致黑色的栈可能指向白色的堆对象,所以必须假定赋值器(mutator)是灰色赋值器,扫描结束之后,必须 STW 重新扫描栈才能确保不丢对象;
  3. STW 重新扫描栈再 goroutine 量大且活跃的场景,延迟不可控,经验值平均 10-100ms;

golang 1.5 之后实现的就是这种类型的插入写屏障。

26d5cda42810214211d9098d234be125.png

删除写屏障

Yuasa-style 屏障 伪代码:
writePointer(slot, ptr)
    shade(*slot)
    *slot = ptr

总结

  1. 删除写屏障也叫基于快照的写屏障方案,必须在起始时,STW 扫描整个栈(注意了,是所有的 goroutine 栈),保证所有堆上在用的对象都处于灰色保护下,保证的是弱三色不变式;

  2. 由于起始快照的原因,起始也是执行 STW,删除写屏障不适用于栈特别大的场景,栈越大,STW 扫描时间越长,对于现代服务器上的程序来说,栈地址空间都很大,所以删除写屏障都不适用,一般适用于很小的栈内存,比如嵌入式,物联网的一些程序;

  3. 并且删除写屏障会导致扫描进度(波面)的后退,所以扫描精度不如插入写屏障;

思考问题:我不整机暂停 STW 栈,而是一个栈一个栈的快照,这样也没有 STW 了,是否可以满足要求?(这个就是当前 golang 混合写屏障的时候做的哈,虽然没有 STW 了,但是扫描到某一个具体的栈的时候,还是要暂停这一个 goroutine 的)

不行,纯粹的删除写屏障,起始必须整个栈打快照,要把所有的堆对象都处于灰色保护中才行。

举例:如果没有把栈完全扫黑,那么可能出现丢数据,如下:

初始状态

  1. A 是 g1 栈的一个对象,g1栈已经扫描完了,并且 C 也是扫黑了的对象;

  2. B 是 g2 栈的对象,指向了  C 和 D,g2 完全还没扫描,B 是一个灰色对象,D 是白色对象;

36a4d5200864bffcfd33856e0098d684.png

步骤一:g2 进行赋值变更,把 C 指向 D 对象,这个时候黑色的 C 就指向了白色的 D(由于是删除屏障,这里是不会触发hook的)

步骤二:把 B 指向 C 的引用删除,由于是栈对象操作,不会触发删除写屏障;

dc6ffa6cfb1f57f11b06c7a80d99f9b7.png

步骤三:清理,因为 C 已经是黑色对象了,所以不会再扫描,所以 D 就会被错误的清理掉。

解决办法有如下

方法一:栈上对象也 hook,所有对象赋值(插入,删除)都 hook(这个就不实际了);

所有的插入,删除如果都 hook ,那么一定都不会有问题,虽然本轮精度很差,但是下轮回收可以回收了。但是还是那句话,栈,寄存器的赋值 hook 是不现实的。

方法二:起始快照整栈跨找,扫黑,使得整个堆上的在用对象都处于灰色保护;

整栈扫黑,那么在用的堆上的对象是一定处于灰色堆对象的保护下的,之后配合堆对象删除写屏障就能保证在用对象不丢失。

方法三:加入插入写屏障的逻辑,C 指向 D 的时候,把 D 置灰,这样扫描也没问题。这样就能去掉起始 STW 扫描,从而可以并发,一个一个栈扫描。

细品下,这不就成了当前在用的混合写屏障了,所以我觉得正确的理解方式应该是:混合写屏障 = 删除写屏障 + 插入写屏障,必须先理解下删除写屏障,你才能理解混合写屏障。

399569055d27e40744860501f28b7fb5.png

混合写屏障

golang 1.5 之后已经实现了插入写屏障,但是由于栈对象赋值无法 hook 的原因,导致扫描完之后还有一次 STW 重新扫描栈的整机停顿,混合写屏障就是解决这个问题的。

论文里的伪代码:

writePointer(slot, ptr):
    shade(*slo
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值