Skip to content

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
}

その内部構造は非常にシンプルです。

  1. done、アトミック値で、実行されたかどうかを示します
  2. 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()
	}
}

コードは全体的に非常にシンプルで、フローは以下の通りです。

  1. アトミック値を直接ロードし、すでに実行済みの場合は直接戻ります
  2. 未実行の場合は、ロックの取得を試みます。ここでは複数のコルーチンがミューテックスロックを競合している可能性があります
  3. 勝者が正常にロックを取得した後、done の値を再度判断する必要があります。ロックを保持している間に、他の誰かがロック取得 - 実行 - ロック解放の一連の操作を完了している可能性があるからです。この場合、あなたはちょうどウェイクアップされたばかりです。
  4. ターゲット関数を実行します
  5. done 値を更新します
  6. ロックを解放します

まとめ

sync.Once は非常に簡潔かつ効率的な同期ツールで、同時実行環境下で特定の操作が 1 回だけ実行されることを保証し、重複作業やリソースの浪費を回避します。

Golang学习网由www.golangdev.cn整理维护