Go by Example: Atomic Counters

Go で状態を管理するための最も重要な仕組みは、 チャネルを使った通信です。これについては、 ワーカープール の例で見ました。 しかし、状態を管理する方法は他にもいくつかあります。 ここでは、複数のゴルーチンからアクセスされる アトミックカウンター (atomic counters) のための、 sync/atomic パッケージを使う方法を見ていきます。

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
)
func main() {

正数カウンターのために、アトミックな整数型を使います。

    var ops atomic.Uint64

WaitGroup は、すべてのゴルーチンがタスクを完了するのを 待つときに使えます。

    var wg sync.WaitGroup

カウンターをちょうど 1000 回インクリメントするゴルーチンを 50 個開始します。

    for i := 0; i < 50; i++ {
        wg.Add(1)
        go func() {
            for c := 0; c < 1000; c++ {

カウンターをアトミックにインクリメントするため、 Add メソッドを使用します。

                ops.Add(1)
            }
            wg.Done()
        }()
    }

すべてのゴルーチンが完了するまで待ちます。

    wg.Wait()

書き込み中のゴルーチンがないことを知っているので、 ops に安全にアクセスできます。ただし、 仮に他のゴルーチンが書き込み中の場合であっても、 Load を使えばアトミックに安全に読み込むことができます。

    fmt.Println("ops:", ops.Load())
}

ちょうど 50,000 オペレーションになるはずです。仮にアトミックでない 整数を使って ops++ でカウンターをインクリメントすると、 ゴルーチン同士が互いに干渉するため、実行するたびに異なる数値が 出力されるはずです。さらに、-race フラグありで実行すると データ競合エラーになるでしょう。

$ go run atomic-counters.go
ops: 50000

次は、状態を管理するもう 1 つの方法である、 ミューテックスを見ていきましょう。

Next example: Mutexes.