I leave uncultivated today, was precisely yesterday perishes tomorrow which person of the body implored。
單例模式做爲一個較爲常見的設計模式,他的定義也很簡單,將類的實例化限制爲一個單個實例
。在Java的世界裏,你可能須要從懶漢模式
、雙重檢查鎖模式
、餓漢模式
、靜態內部類
、枚舉
等方式中選擇一種手動擼一遍代碼,可是他們操做起來很容易一不當心就會出現bug。而在Go裏,內建提供了保證操做只會被執行一次的sync.Once
,操做起來及其簡單。web
在開發過程當中須要單例模式的場景比較常見,好比web開發過程當中,不可避免的須要跟DB打交道,而DB管理器初始化一般須要保證有且僅發生一次。那麼使用sync.Once
實現起來就比較簡單了。設計模式
`var manager *DBManager` `var once sync.Once` `func GetDBManager()*DBManager{` `once.DO(func(){` `manager = &DBManager{}` `manager.initDB(config)` `})` `return manager` `}`
能夠看到僅僅須要once.DO(func(){...})
便可, 開發者只須要關注本身的初始化程序便可,單例由sync.Once
來保證,極大下降了開發者的心智負擔。安全
sync.Once
結構也比較簡單,只有一個uint32
字段和一個互斥鎖Mutex
。數據結構
`// 一旦使用不容許被拷貝` `type Once struct {` `// done表示當前的操做是否已經被執行 0表示尚未 1表示已經執行` `// done屬性放在結構體的第一位,是由於它在hot path中使用` `// hot path在每一個調用點會被內聯。` `// 將done放在結構體首位,像amd64/386等架構上能夠容許更多的壓縮指令` `// 而且在其餘架構上更少的指令去計算偏移量` `done uint32` `m Mutex` `}`
sync.Once
的核心原理,是利用sync.Mutex
和atomic
包的原子操做來完成。done
表示是否成功完成一次執行。存在兩個狀態:架構
sync.Once
的第一次DO
操做還沒有成功sync.Once
的第一次DO
操做已經完成每次DO
方法調用都會去檢查done
的值,若是爲1則啥也不作;若是爲0則進入doSlow
流程,doSlow
很巧妙的先使用sync.Mutex
。這樣若是併發場景,只有一個goroutine
會搶到鎖執行下去,其餘goroutine
則阻塞在鎖上,這樣的好處是若是拿到鎖的那個goroutine
失敗,其餘阻塞在鎖上的goroutine
就是預備隊替補上去。確保sync.Once
有且僅成功執行一次的語義。併發
once flow graph 函數
好了,接下來看源碼源碼分析
Do
執行函數f
當且僅當對應sync.Once
實例第一次調用Do
。換句話說,給定var once Once
,若是once.Do(f)
被調用了屢次,,儘管f
在每次調用的值均不一樣,但只有第一次調用會執行f
。若是須要每一個函數都執行,則須要新的sync.Once
實例。ui
`// Do的做用主要是針對初始化且有且只能執行一次的場景。由於Do直到f返回才返回,` `// 因此若是f內調用Do則會致使死鎖` `// 若是f執行過程當中panic了 那麼Do任務f已經執行完畢 將來再次調用不會再執行f` `func (o *Once) Do(f func()) {` `if atomic.LoadUint32(&o.done) == 0 {//判斷f是否被執行` `// 可能會存在併發 進入slow-path` `o.doSlow(f)` `}` `}`
註釋裏提到了一種不正確的Do
的實現atom
`if atomic.CompareAndSwapUint32(&o.done, 0, 1) {` `f()` `}`
這種實現不正確的緣由在於,沒法保證f()
有且僅執行一次的語義。由於使用直接CAS來解決問題,若是同時有多個goroutine
競爭執行Do
那麼是能保證有且僅有一個goroutine
會獲得執行機會,其餘goroutine
只能默默離開。
可是若是得到執行機會的goroutine
執行失敗了,那麼之後f()
就在也沒有執行機會了。
那麼咱們來看看官方的實現方式
`func (o *Once) doSlow(f func()) {` `o.m.Lock()` `defer o.m.Unlock()` `if o.done == 0 {//二次判斷f是否已經被執行` `defer atomic.StoreUint32(&o.done, 1)` `f()` `}` `}`
官方的作法就是若是多個goroutine
都來競爭Do
,那麼先讓一個goroutine
拿到sync.Mutex
的鎖,其餘的goroutine
先不着急讓他們直接返回,而是都先阻塞在sync.Mutex
上。若是那個拿到鎖的goroutine
很不幸執行f()
失敗了,那麼defer o.m.Unlock()
操做會馬上喚醒阻塞的goroutine
接着嘗試執行直到成功爲止。執行成功後經過defer atomic.StoreUint32(&o.done, 1)
來將執行f()
的大門給關閉上。
有了sync.Once
,相比Java或者Python實現單例更加簡單,不用殫精竭慮懼怕手抖寫出引起線程安全問題的代碼了。
若是閱讀過程當中發現本文存疑或錯誤的地方,能夠關注公衆號留言。若是以爲還能夠 幫忙點個在看😁