資料閱讀

有兩張不錯的gif圖(原子wikipedia) https://zhuanlan.zhihu.com/p/74853110

最先看的文章,引用了上面的文章 https://www.jianshu.com/p/4c5a303af470

在討論 Go 的混合寫屏障 https://github.com/changkun/go-under-the-hood/issues/20

解釋了爲啥有 read barrier https://www.zhihu.com/question/42879518/answer/437304734

這些個文章看完,總覺得其中對golang的混合寫屏障沒說透,甚至感覺有錯誤說法。

三色標記的理解

基本的mark-sweep算法

基本算法有個缺點,需要STW(stop the world),有較大延時。

三色標記法(tricolor mark-sweep)

三色標記的過程中,節點顏色變化:白色》灰色》黑色。當沒有灰色了,標記結束,黑色存活,白色清除。

三色標記可以實現增量回收和併發回收,能降低延時(latency);當然也有缺點,會降低吞吐量(throughput)。

三色標記正確運行需要滿足下面2個條件中的一個:

  1. 強三色不變式:黑色對象不引用白色對象。
  2. 弱三色不變式:黑色對象引用的白色對象可以通過灰色對象搜索到。

當使用增量回收和併發回收時,回收過程中,用戶程序會新建對象修改對象引用,會破壞上面的條件。寫屏障就是爲了處理這個事情。

有兩套方案

// 當obj是黑色時,標記插入的*prt對象至少爲灰色。滿足強三色條件
// GC運行時,新new出來的對象,可以直接標記成黑色。
djjkstra_write_barrier(obj, ref, ptr){
    shade(ptr) // shade的做法時如果是白色就標記成灰色,否則不變。後面的僞代碼都是這個意思。
    ref = ptr
}

// 從obj上刪除ref時,標記*ref至少爲灰色。這樣可以滿足弱三色條件
// 悲觀認爲所有被刪的對象都可能被黑色對象引用了,效率很低的樣子。
// **注意:**GC運行時,新new出的對象必須直接標記成黑色
yuasa_write_barrier(obj, ref, ptr){
    shade(ref) // 當obj時黑色時,可以不標記,因爲刪掉黑色到白色之類引用關係不破壞弱三色條件
    ref = ptr
}

一般來說dijk就挺好的,但像golang這種希望寫棧上的引用時不使用寫屏蔽,提高性能,所以得找別的方法。

  1. golang早期用的dijk的方法,得把棧本身重新標記成灰色,mark的最後階段STW,然後掃描下棧,完成標記。這樣說是最後結算的延時可以達到100ms。
  2. golang 1.8 用yuasa的方法,也同樣存在同樣的問題。這時可以當棧還不是黑色時,所有複製操作,額外把ptr標記成灰色,就當是從棧裏刪除下來的。
    僞代碼
// golang 1.8的方式,說法是結合了dij和yuasa。效率(吞吐量)比yuasa還低。
// **注意:**GC運行時,新new出的對象必須直接標記成黑色
golang_hybird_write_barrier(obj, ref, ptr){
    shade(ref)
    // 當棧本身還沒完成掃描時,假定ptr就是從棧上刪除取下來的對象。當棧掃描完,可以當成標記爲黑色,這時就不需要補充標記了。
    if current stack is grey: 
        shade(ptr)
    ref = ptr
}

這樣就可以實現,棧裏面的寫操作不需要barrier處理,掃描最後也不需要STW重新掃描棧。

歡迎關注我們的微信公衆號,每天學習Go知識

相關文章