サンプルで学ぶ Go 言語:Mutexes

前の例で、単純なカウンタの状態をアトミックな操作で管理する方法を紹介した。 より複雑な状態を管理したいときには、ミューテックス(mutex)を使って複数のゴルーチンから安全にデータを読み書きすることもできる。

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

状態の例としてマップを使う。

    var state = make(map[int]int)

この mutex を使って、state へのアクセスを同期する。

    var mutex = &sync.Mutex{}

読み操作・書き操作がそれぞれ何度行われたかをカウントする。

    var readOps uint64
    var writeOps uint64

100 個のゴルーチンを作り、それぞれが1ミリ秒ごとに state を繰り返し読み出す。

    for r := 0; r < 100; r++ {
        go func() {
            total := 0
            for {

読み出しのたびに、アクセスするキーを選ぶ。 Lock()mutex をロックし、state への排他的なアクセスを獲得し、値を読み出し、Unlock()mutex のロックを解除し、最後に readOps の値を1増やす。

                key := rand.Intn(5)
                mutex.Lock()
                total += state[key]
                mutex.Unlock()
                atomic.AddUint64(&readOps, 1)

読み出しのたびに少し待つ。

                time.Sleep(time.Millisecond)
            }
        }()
    }

10個のゴルーチンを起動し、読み出しと同様のパターンで書き込みを行う。

    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)
                mutex.Lock()
                state[key] = val
                mutex.Unlock()
                atomic.AddUint64(&writeOps, 1)
                time.Sleep(time.Millisecond)
            }
        }()
    }

10個のゴルーチンが statemutex で仕事をできるよう、一秒待つ。

    time.Sleep(time.Second)

結果を読み出して、読み書き操作のカウントを報告する

    readOpsFinal := atomic.LoadUint64(&readOps)
    fmt.Println("readOps:", readOpsFinal)
    writeOpsFinal := atomic.LoadUint64(&writeOps)
    fmt.Println("writeOps:", writeOpsFinal)

state をロックして、最終的にどうなったかを表示する。

    mutex.Lock()
    fmt.Println("state:", state)
    mutex.Unlock()
}

プログラムを実行した結果、mutex で同期した操作を合計で約90000回実行したことがわかる。

$ go run mutexes.go
readOps: 83285
writeOps: 8320
state: map[1:97 4:53 0:33 2:15 3:2]

続いて、同様の状態管理をゴルーチンとチャネルだけを使って実装する方法を紹介する。

次の例:Stateful Goroutines