sync.Once的底層實(shí)現(xiàn)


首先:保證變量?jī)H被初始化一次,需要有個(gè)標(biāo)志來(lái)判斷變量是否已初始化過(guò),若沒(méi)有則需要初始化。

第二:線程安全,支持并發(fā),無(wú)疑需要互斥鎖來(lái)實(shí)現(xiàn)。

package sync

import (
    "sync/atomic"
)

type Once struct {
    done uint32
    m    Mutex
}

func (o *Once) Do(f func()) {
    if atomic.LoadUint32(&o.done) == 0 {
        // 快速路經(jīng)檢測(cè),無(wú)鎖情況下保證原子性和跨CPU可見(jiàn)性
        o.doSlow(f)
    }
}

func (o *Once) doSlow(f func()) {
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 { 
        // mutex 能保證解鎖后其他協(xié)程對(duì)done的修改的可見(jiàn)性,所以不需要atomic方法
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}

為什么將 done 置為 Once 的第一個(gè)字段?

done 在熱路徑中,done 放在第一個(gè)字段,能夠減少 CPU 指令,也就是說(shuō),這樣做能夠提升性能。

熱路徑(hot path)是程序非常頻繁執(zhí)行的一系列指令,sync.Once 絕大部分場(chǎng)景都會(huì)訪問(wèn) o.done,在熱路徑上是比較好理解的,如果 hot path 編譯后的機(jī)器碼指令更少,更直接,必然是能夠提升性能的。

為什么放在第一個(gè)字段就能夠減少指令呢?因?yàn)榻Y(jié)構(gòu)體第一個(gè)字段的地址和結(jié)構(gòu)體的指針是相同的,如果是第一個(gè)字段,直接對(duì)結(jié)構(gòu)體的指針解引用即可。如果是其他的字段,除了結(jié)構(gòu)體指針外,還需要計(jì)算與第一個(gè)值的偏移(calculate offset)。在機(jī)器碼中,偏移量是隨指令傳遞的附加值,CPU 需要做一次偏移值與指針的加法運(yùn)算,才能獲取要訪問(wèn)的值的地址。因?yàn)?,訪問(wèn)第一個(gè)字段的機(jī)器代碼更緊湊,速度更快。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容