同時実行 Go の原子カウンター

Beginner

This tutorial is from open-source community. Access the source code

はじめに

この実験では、複数の goroutine によってアクセスされる原子カウンターに対して sync/atomic パッケージを使用して Go で状態を管理することに焦点を当てています。

原子カウンター

問題は、50 個の goroutine と sync/atomic パッケージを使用して、カウンターを正確に 1000 回インクリメントすることです。

  • sync/atomic パッケージを使用してカウンターをインクリメントします。
  • すべての goroutine が作業を完了するのを待つために WaitGroup を使用します。
## 正確に 50,000 回の操作が行われることが期待されます。もし非原子的な `ops++` を使ってカウンターをインクリメントした場合、
## おそらく異なる数が得られ、実行ごとに変化します。なぜなら goroutine 同士が相互に干渉するからです。さらに、`-race` フラグを付けて実行すると、データ競合エラーが発生します。
$ go run atomic-counters.go
ops: 50000

## 次に、状態管理の別のツールであるミューテックスについて見ていきます。

以下に完全なコードがあります:

// Go で状態管理の主なメカニズムは、チャネルを通じた通信です。例えば [ワーカープール](worker-pools) で見たようにです。ただし、状態管理には他にもいくつかのオプションがあります。ここでは、複数の goroutine によってアクセスされる _原子カウンター_ のために `sync/atomic` パッケージを使用する方法を見ていきます。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {

    // 常に正のカウンターを表すために、符号なし整数を使用します。
    var ops uint64

    // WaitGroup は、すべての goroutine が作業を完了するのを待つのに役立ちます。
    var wg sync.WaitGroup

    // それぞれがカウンターを正確に 1000 回インクリメントする 50 個の goroutine を起動します。
    for i := 0; i < 50; i++ {
        wg.Add(1)

        go func() {
            for c := 0; c < 1000; c++ {
                // カウンターを原子的にインクリメントするには、`AddUint64` を使用し、`&` 構文を使って `ops` カウンターのメモリアドレスを渡します。
                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

    // すべての goroutine が完了するのを待ちます。
    wg.Wait()

    // 他の goroutine が `ops` に書き込んでいないことがわかっているので、今は `ops` にアクセスするのが安全です。更新中に原子的な値を安全に読み取ることも、`atomic.LoadUint64` のような関数を使って可能です。
    fmt.Println("ops:", ops)
}

まとめ

この実験では、複数の goroutine を使ってカウンターをインクリメントすることで、Go で状態を管理するために sync/atomic パッケージをどのように使用するかを学びました。AddUint64 関数を使ってカウンターを原子的にインクリメントし、すべての goroutine が作業を完了するのを待つために WaitGroup を使用しました。