once
sync.Once 是 Go 標准庫中的一個同步工具,用於確保某個函數在並發環境下只執行一次。它通常用於延遲初始化、全局資源的初始化等場景,確保某個特定的操作只會被執行一次,即使有多個協程並發執行。
go
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var once sync.Once
for i := range 10 {
wg.Add(1)
go func() {
defer wg.Done()
once.Do(func() {
fmt.Println(i)
})
}()
}
wg.Wait()
}在上面的代碼中,總共有 10 個協程,但無論如何,只有一個協程會執行 once.Do() 中的 fmt.Println(i)。具體是哪一個協程會打印,取決於哪個協程最先到達 once.Do()。
結構
go
type Once struct {
done atomic.Uint32
m Mutex
}它的內部結構非常簡單
done,一個原子值,用於表示是否被執行過m,互斥鎖,用於阻塞其它想要執行的協程
它的原理就是在執行前先加鎖,然後更新done,執行完畢後解鎖。
Do
go
func (o *Once) Do(f func()) {
if o.done.Load() == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done.Load() == 0 {
defer o.done.Store(1)
f()
}
}代碼整體上非常簡單,流程如下:
- 直接加載原子值,如果已經執行過了就直接返回
- 沒有執行過,則嘗試持有鎖,這裡可能是多個協程在競爭互斥鎖
- 當勝者成功持有鎖後,還需要再一次判斷
done的值,因為在你持有鎖後,別人可能已經完成加鎖-執行-解鎖一條龍了,這時你才剛被喚醒。 - 執行目標函數
- 更新
done值 - 釋放鎖
小結
sync.Once 是一個非常簡潔而高效的同步工具,它確保了並發環境下某個操作只執行一次,從而避免了重復工作或資源的浪費。
