Go による例: ミュテックス

前の例では、アトミック演算を使用して、単純なカウンターの状態を管理する方法について説明しました。さらに複雑な状態の場合は、mutexを使用して、複数のゴルーチン間でデータに安全にアクセスできます。

package main
import (
    "fmt"
    "sync"
)

Container にはカウンターのマップが含まれます。これは、複数のゴルーチンから同時に更新したいので、Mutex を追加してアクセスを同期させます。ミューテックスはコピーしない必要があることに注意してください。このstruct を受け渡す場合は、ポインターで行う必要があります。

type Container struct {
    mu       sync.Mutex
    counters map[string]int
}

counters にアクセスする前にミューテックスをロックします。関数の末尾で、defer ステートメントを使用してロックを解除します。

func (c *Container) inc(name string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.counters[name]++
}

ミューテックスの初期値はそのまま使用できるので、ここでは初期化は必要ありません。

func main() {
    c := Container{
        counters: map[string]int{"a": 0, "b": 0},
    }
    var wg sync.WaitGroup

この関数は、ループ内で指定されたカウンターの値をインクリメントします。

    doIncrement := func(name string, n int) {
        for i := 0; i < n; i++ {
            c.inc(name)
        }
        wg.Done()
    }

複数のゴルーチンを同時に実行します。すべて同じContainer にアクセスし、2 つは同じカウンターにアクセスすることに注意してください。

    wg.Add(3)
    go doIncrement("a", 10000)
    go doIncrement("a", 10000)
    go doIncrement("b", 10000)

ゴルーチンが完了するのを待ちます。

    wg.Wait()
    fmt.Println(c.counters)
}

プログラムを実行すると、カウンターが期待どおりに更新されたことがわかります。

$ go run mutexes.go
map[a:20000 b:10000]

次に、ゴルーチンとチャンネルのみを使用して、この同じ状態管理タスクを実装する方法について説明します。

次の例: 状態のあるゴルーチン