文章始發於公衆號【邁莫coding】 https://mp.weixin.qq.com/s/b89PmljELaPaVuLw-YIQKg
Once 能夠用來執行且僅僅執行一次動做,經常用於單例對象的初始化場景。程序員
Once 經常用來初始化單例資源,或者併發訪問只需初始化一次的共享資源,或者在測試的時候初始化一次測試資源。面試
sync.Once 只暴露了一個方法 Do,你能夠屢次調用 Do 方法,可是隻有第一次調用 Do 方法時 f 參數纔會執行,這裏的 f 是一個無參數無返回值的函數。微信
就拿我負責的一個項目來講,由於項目的配置是掛在第三方平臺上,因此在項目啓動時須要獲取資源配置,由於須要一個方法來保證配置僅此只獲取一次,所以,咱們考慮使用 sync.Once 來獲取資源。這樣的話,能夠防止在其餘地方調用獲取資源方法,該方法僅此執行一次。閉包
下面我簡單寫個Demo來演示一個sync.Once如何使用併發
package main import ( "fmt" "sync" ) var once sync.Once var con string func main() { once.Do(func() { con = "hello Test once.Do" }) fmt.Println(con) }
代碼說明:框架
代碼的話比較簡單,就是經過調用Do方法,採用閉包方式,將字符串("hello Test once.Do")賦值給con,進而打印出值,這就是 sync.Once 的使用,比較容易上手。函數
但咱們用一個方法或者框架時,若是不對其瞭如指掌,總有點不太靠譜,感受內心不踏實。爲此,咱們來聊一聊 sync.Once 的源碼實現,讓他無處可遁。源碼分析
接下來分析 sync.Do 到底是如何實現的,它存儲在包sync下 once.go 文件中,源代碼以下:測試
// sync/once.go type Once struct { done uint32 // 初始值爲0表示還未執行過,1表示已經執行過 m Mutex } func (o *Once) Do(f func()) { // 判斷done是否爲0,若爲0,表示未執行過,調用doSlow()方法初始化 if atomic.LoadUint32(&o.done) == 0 { // Outlined slow-path to allow inlining of the fast-path. o.doSlow(f) } } // 加載資源 func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() // 採用雙重檢測機制 加鎖判斷done是否爲零 if o.done == 0 { // 執行完f()函數後,將done值設置爲1 defer atomic.StoreUint32(&o.done, 1) // 執行傳入的f()函數 f() } }
接下來會分爲兩大部分進行分析,第一部分爲 Once 的結構體組成結構,第二部分爲 Do 函數的實現原理,我會在代碼上加上註釋,保證用心閱讀完都有收穫。ui
type Once struct { done uint32 // 初始值爲0表示還未執行過,1表示已經執行過 m Mutex }
首先定義一個struct結構體 Once ,裏面存儲兩個成員變量,分別爲 done 和 m 。
done成員變量
m成員變量
func (o *Once) Do(f func()) { // 判斷done是否爲0,若爲0,表示未執行過,調用doSlow()方法初始化 if atomic.LoadUint32(&o.done) == 0 { // Outlined slow-path to allow inlining of the fast-path. o.doSlow(f) } } // 加載資源 func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() // 採用雙重檢測機制 加鎖判斷done是否爲零 if o.done == 0 { // 執行完f()函數後,將done值設置爲1 defer atomic.StoreUint32(&o.done, 1) // 執行傳入的f()函數 f() }
調用 Do 函數時,首先判斷done值是否爲0,若爲1,表示傳入的匿名函數 f() 已執行過,無需再次執行;若爲0,表示傳入的匿名函數 f() 還未執行過,則調用 doSlow() 函數進行初始化。
在 doSlow() 函數中,若併發的goroutine進入該函數中,爲了保證僅有一個goroutine執行 f() 匿名函數。爲此,須要加互斥鎖保證只有一個goroutine進行初始化,同時採用了雙檢查的機制(double-checking),再次判斷 o.done 是否爲 0,若是爲 0,則是第一次執行,執行完畢後,就將 o.done 設置爲 1,而後釋放鎖。
即便此時有多個 goroutine 同時進入了 doSlow 方法,由於雙檢查的機制,後續的 goroutine 會看到 o.done 的值爲 1,也不會再次執行 f。
這樣既保證了併發的 goroutine 會等待 f 完成,並且還不會屢次執行 f。
文章也會持續更新,能夠微信搜索「 邁莫coding 」第一時間閱讀。天天分享優質文章、大廠經驗、大廠面經,助力面試,是每一個程序員值得關注的平臺。