はじめに
この実験では、複数の 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 を使用しました。