once
sync.Once は Go 標準ライブラリの同期ツールで、同時実行環境下で特定の関数が 1 回だけ実行されることを保証するために使用されます。これは遅延初期化、グローバルリソースの初期化などのシーンで一般的に使用され、特定の操作が 1 回だけ実行されることを保証します。たとえ複数のコルーチンが同時に実行されてもです。
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 個のコルーチンがありますが、无论如何、1 つのコルーチンのみが 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 は非常に簡潔かつ効率的な同期ツールで、同時実行環境下で特定の操作が 1 回だけ実行されることを保証し、重複作業やリソースの浪費を回避します。
