深度解析Golang sync.Once源碼

目錄

  • 什麼是sync.Once
  • 如何使用sync.Once
  • 源碼分析
文章始發於公衆號【邁莫coding】 https://mp.weixin.qq.com/s/b89PmljELaPaVuLw-YIQKg

什麼是sync.Once

Once 能夠用來執行且僅僅執行一次動做,經常用於單例對象的初始化場景。程序員

Once 經常用來初始化單例資源,或者併發訪問只需初始化一次的共享資源,或者在測試的時候初始化一次測試資源。面試

sync.Once 只暴露了一個方法 Do,你能夠屢次調用 Do 方法,可是隻有第一次調用 Do 方法時 f 參數纔會執行,這裏的 f 是一個無參數無返回值的函數。微信

如何使用sync.Once

就拿我負責的一個項目來講,由於項目的配置是掛在第三方平臺上,因此在項目啓動時須要獲取資源配置,由於須要一個方法來保證配置僅此只獲取一次,所以,咱們考慮使用 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 ,裏面存儲兩個成員變量,分別爲 donem

done成員變量

  • 1表示資源未初始化,須要進一步初始化
  • 0表示資源已初始化,無需初始化,直接返回便可

m成員變量

  • 爲了防止多個goroutine調用 doSlow() 初始化資源時,形成資源屢次初始化,所以採用 Mutex 鎖機制來保證有且僅初始化一次

Do

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 」第一時間閱讀。天天分享優質文章、大廠經驗、大廠面經,助力面試,是每一個程序員值得關注的平臺。
相關文章
相關標籤/搜索