🏃♂️
Go の Mutex と -race オプション
まずはこのコードを実行してみます。
package main
import (
"fmt"
"sync"
)
func main() {
var a []int
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
a = append(a, i)
}(i)
}
wg.Wait()
fmt.Println(len(a))
}
このコードでは a というスライスに1,000個の要素を追加しています。なので実行すると 1000
が出力されるはずですが、実際には 949
とか 933
といった数字になります。
これはforループ内のgoルーチンが並列に実行されるからですね。
こういったスレッドアンセーフなコードを検出するためのツールが、Goでは標準で用意されています。
以下のように、-race
をつけて実行してみましょう。
$ go run -race mutex-test.go
==================
WARNING: DATA RACE
Read at 0x00c000110150 by goroutine 7:
....
Previous write at 0x00c000110150 by goroutine 6:
....
このようにレースコンディションになった箇所を表示してくれます。
原因がわかったので、修正してみます。
といってもMutexで該当箇所を保護するだけです。
func main() {
var a []int
var mutex = new(sync.Mutex) // 追加
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
mutex.Lock() // 追加
a = append(a, i)
mutex.Unlock() // 追加
}(i)
}
wg.Wait()
fmt.Println(len(a))
}
これを実行すると、期待した 1000
が出力されます。
-race
をつけて実行しても、先ほどのワーニングは出力されません。
この種のバグは見つけにくいのですが、Goだと簡単に切り分けができ、修正の確認もしやすのでとても便利ですね。
Discussion