メインコンテンツにスキップ
モジュール 7 · レッスン 4 / 5コース内の 34/50~14 min
モジュールのレッスン (4/5)

`sync.Mutex` と `sync.WaitGroup`

チャネルは Go の主要なイディオムですが、より伝統的な場合もあります プリミティブが必要です。 sync パッケージはミューテックスを提供します。お待ちください。 グループやその他のユーティリティを使用して共有状態を調整します。

sync.Mutex: 排他ロック

Go
import "sync"

type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

Lock() と Unlock() の間に入るゴルーチンは一度に 1 つ だけです。 必須パターン: Lock() の直後の defer mu.Unlock()。

sync.RWMutex: 多数のリーダー、1 つのライター

読み取りが多い状態の場合:

Go
var mu sync.RWMutex

mu.RLock()              // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()

mu.Lock()               // write lock: esclusivo
data = newValue
mu.Unlock()

読み取りが書き込みよりも実際に優勢な場合にのみ役立ちます。それ以外の場合はレギュラー sync.Mutex はよりシンプルで、内部の処理能力が低いため、多くの場合高速です。 論争。

sync.WaitGroup: N 個のゴルーチンを待機します

Go
var wg sync.WaitGroup

for i := 0; i < 3; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        work(id)
    }(i)
}
wg.Wait()    // blocca finché tutti i Done() sono arrivati

ワークフロー:

  1. wg.Add(N) は、(ゴルーチンを起動する前に) カウンタを N ずつインクリメントします。
  2. 各ゴルーチンは、終了時に wg.Done() を呼び出します (理想的には defer 経由)。
  3. wg.Wait() は、カウンターが 0 に戻るまでブロックします。

sync.Once: スレッドセーフな遅延初期化

Go
var (
    once   sync.Once
    config *Config
)

func GetConfig() *Config {
    once.Do(func() {
        config = loadConfig()
    })
    return config
}

once.Do のコールバックは、同時実行下でも 1 回だけ実行されます。 呼び出します。シングルトン/遅延初期化パターン。

ミューテックスとチャネル: いつ何を使用するか

ガイドライン (Go の公式 FAQ からも):

ケース推奨ツール
データの所有権の譲渡チャンネル
作業の分散 (ワークキュー)チャンネル
独立したゴルーチンを調整するチャンネル
構造体フィールドの保護ミューテックス
キャッシュ/カウンターミューテックス またはアトミック
シングルトンリファレンスsync.Once

「オーケストレーションするチャネル、共有データのミューテックス」は良い経験則です。

レースディテクター

-race を使用してテストを実行し、データ競合を特定します。

Bash
go test -race ./...
go run -race main.go

競合検出器はバイナリを計測し、すべての非同期をログに記録します。 共有メモリへのアクセス。同時実行コードを検証するためのツール #1 生産前。

試してみてください

運動#go.m7.l4.e1
試行回数: 0読み込み中…

mu.Lock() + defer mu.Unlock() でカウントの増分を保護します。

エディターを読み込み中…
ヒントを表示

Lock() の直後に mu.Unlock() を遅延させると、パニック時でも解放が保証されます。

3 回の試行後に解決策が利用可能になります

運動#go.m7.l4.e2
試行回数: 0読み込み中…

3 つのゴルーチンを起動し、sync.WaitGroup (各ゴルーチンの Add(3)、Done()、Wait()) を使用してすべてのゴルーチンを待ちます。

エディターを読み込み中…
ヒントを表示

goroutine を起動する前に追加します。 defer を使用して goroutine 内で実行されます。

3 回の試行後に解決策が利用可能になります

Quiz#go.m7.l4.e3
準備完了

ミューテックスが確実に解放されるように推奨されるパターンは何ですか?

Go
mu.Lock()
// ?
回答の選択肢

要約

  • sync.Mutex は共有状態を保護します。常に defer mu.Unlock()。
  • Use a pointer receiver to avoid copying the mutex.
  • sync.RWMutex for many-readers/one-writer patterns (only if it helps).
  • sync.WaitGroup: go の前の Add、Done と defer、Wait を待機します。
  • sync.Once: thread-safe lazy initialization.
  • "Channels to orchestrate, mutexes for shared data".
  • go test -race: tool #1 to spot data races.