サンプルで学ぶ Go 言語:Atomic Counters

Go で状態を管理するとき、まず検討すべきはチャネル通信を使う方法である。 このような例は既にワーカープールで紹介した。

//

別のやり方もある。 ここでは、sync/atomic パッケージにある、複数のゴルーチンからアクセスされるアトミックなカウンタを使う方法を紹介する。

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

符号無し整数型の変数で(負の値を取らない)カウンタを表現する

    var ops uint64

WaitGroup を使ってすべてのゴルーチンが仕事を終えるのを待つ

    var wg sync.WaitGroup

更新を並行に実行するため、50個のゴルーチンを起動する。 各ゴルーチンは約1ミリ秒ごとにカウンタの値を1増やす。

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

カウンタを増やすにはAddUint64 を使う。 この関数にはカウンタ ops のメモリアドレスを渡す。 演算子 & を使って変数のアドレスを取得できる。

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

すべてのゴルーチンが仕事を終えるのを待つ

    wg.Wait()

ゴルーチンの書き込みは終わっているので ops に安全にアクセスできる。 ゴルーチンが書き込んでいる途中に読み出したければ atomic.LoadUint64 を使う。

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

ちょうど50000回操作を実行するはずだ。 このときにアトミックではない ops++ を使うと、ゴルーチンが相互に干渉しあうので、 カウンタの値は実行のたびに異なる(そして50000でもない)値になるだろう。 -race フラグを使ってデータアクセスの競合を検出することもできる。

$ go run atomic-counters.go
ops: 50000

次の例では、状態管理のためのもう一つのツールである、 ミューテックスを紹介する。

次の例:Mutexes