Atomic Counters

Atomic Counters #

// Go で状態を管理するとき、まず検討すべきはチャネル通信を使う方法である。
// このような例は既に[ワーカープール](./worker-pools.html)で紹介した。
//
// 別のやり方もある。
// ここでは、`sync/atomic` パッケージにある、複数のゴルーチンからアクセスされる<em>アトミックなカウンタ</em>を使う方法を紹介する。

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)

		go func() {
			for c := 0; c < 1000; c++ {
				// カウンタを増やすには`AddUint64` を使う。
				// この関数にはカウンタ `ops` のメモリアドレスを渡す。
				// 演算子 `&` を使って変数のアドレスを取得できる。
				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

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