sync.Once 是 Go 標準庫提供的使函式只執行一次的實作,作用與 init 函式類似,但有區別,在某些情況下預先初始化一個變數會增加函式的啟動延遲,如果實際執行時可能用不上這個變數,那么初始化就是非必須的,sync.Once很好的解決了這個問題,Once可以在任意的位置呼叫,并且只會執行一次,并發場景下是執行緒安全的,
Do方法
Once只提供了一個 func (o *Once) Do(f func()) 方法,Do方法呼叫一個函式,這里有三種情況:
- 如果函式沒有被呼叫則呼叫它,
- 如果函式已經被呼叫完成則不呼叫.
- 如果函式正在被呼叫(未完成),則等待函式呼叫完成再回傳,
func main() {
var once sync.Once
onceBody := func(){
fmt.Println("Only once")
}
done := make(chan bool)
for i:=0; i<10; i++ {
go func() {
once.Do(onceBody)
done <- true
}()
}
for i:=0; i<10; i++ {
<-done
}
}
// 結果:
// Only once
Once原始碼
Once結構
// Once 是一個將執行一個動作的物件,
type Once struct {
// done 指示操作是否已執行,它在結構中是第一個,因為它用于熱路徑,熱路徑在每個呼叫站點行內,
// 首先放置允許在某些架構(amd64x86)上使用更緊湊的指令,而在其他架構上使用更少的指令(計算偏移量),
done uint32
m Mutex
}
Once結構由兩個欄位一個 uint32型別的done來標志操作是否執行,這個欄位放在第一個的原因是可以使用偏移量的形式來獲取,然后就是一個互斥鎖,
Do實作
func (o *Once) Do(f func()) {
// 注意:這是 Do 的錯誤實作: if atomic.CompareAndSwapUint32(&o.done, 0, 1) { f() }
// Do 保證當它回傳時, f 已經完成,這個實作不會實作這個保證:
// 給定兩個同時呼叫,cas 的獲勝者將呼叫 f,第二個將立即回傳,
// 而不等待第一個對 f 的呼叫完成,這就是為什么慢速路徑回退到互斥體,
// 以及為什么 atomic.StoreUint32 必須延遲到 f 回傳之后,
if atomic.LoadUint32(&o.done) == 0 {
// 慢速路徑以允許行內快速路徑,
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
// 快速路徑是指做功較少的路徑,而慢速路徑是指做功較多的路徑,
這里可以看到如果第二次呼叫函式時發現第一次呼叫還未呼叫結束,會在doSlow中等待解鎖,這里用一個互斥鎖避免第一次呼叫未結束,第二次呼叫就回傳的情況,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/442763.html
標籤:Go
