Go by Example: Atomic Counters

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

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

正数カウンターのために符号なし整数を使います。

    var ops uint64

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

    var wg sync.WaitGroup

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

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

カウンターをアトミックにインクリメントするため、 & 構文で ops カウンターのメモリアドレスを AddUint64 に与えます。

        go func() {
            for c := 0; c < 1000; c++ {
                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

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

    wg.Wait()

書き込み中のゴルーチンがないことを知っているので、 ops に安全にアクセスできます。atomic.LoadUint64 のような関数を使えば、更新され続けているカウンターを安全に 読み込むことも可能です。

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

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

$ go run atomic-counters.go
ops: 50000

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

Next example: Mutexes.