目錄
- 什么是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 函式的實作原理,我會在代碼上加上注釋,保證用心閱讀完都有識訓,
結構體
type Once struct {
done uint32 // 初始值為0表示還未執行過,1表示已經執行過
m Mutex
}
首先定義一個struct結構體 Once ,里面存盤兩個成員變數,分別為 done 和 m ,
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 」第一時間閱讀,每天分享優質文章、大廠經驗、大廠面經,助力面試,是每個程式員值得關注的平臺,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/258484.html
標籤:區塊鏈
上一篇:go字串處理
