概要
Go1.6からマップへの同時アクセスを検知する機能が追加されました。
しかしそれ以前からrace detectorが標準で備わっているため、複数のスレッドによる同時アクセスで起こるデータ競合の検知はできます。
今回はその使い方を紹介します。
環境
- Go 1.6
使い方
-race
オプションを付けます。
$ go run -race xxx.go
や
$ go test -race mypkg
のように実行すればOKです。
サンプル
例えば以下の様なコードがあったとします。
package main import "fmt" func main() { m := make(map[int]int) go func() { for i := 0; i < 10; i++ { m[i] = i + 1 } }() for i := 0; i < 10; i++ { fmt.Printf("%d\n", m[i]) } }
これに対して通常の実行をすると
$ go run test.go 0 0 0 0 0 0 0 0 0 0
となり、一見問題無さそうに見えますが、race detectorを使うと
$ go run -race test.go 0 0 0 0 0 0 ================== WARNING: DATA RACE0 Write by goroutine 6: 0 runtime.mapassign1() /usr/local/opt/go/libexec/src/runtime/hashmap.go:429 +0x0 main.main.func1() /Users/xxxxx/test.go:9 +0x6b 0 Previous read by main goroutine: runtime.mapaccess1_fast64() /usr/local/opt/go/libexec/src/runtime/hashmap_fast.go:103 +0x0 0 main.main() /Users/xxxxx/test.go:13 +0xb9 Goroutine 6 (running) created at: main.main() /Users/xxxxx/test.go:11 +0x89 ================== Found 1 data race(s) exit status 66
こんな感じで問題を指摘してくれます。
調べ方
ポイントは
- Write by goroutine n:
- Previous read by main goroutine:
や
- Read by goroutine n:
- Previous write by goroutine m:
のように何かしらの変数がgoroutineによってreadとwriteが同時に行われた箇所を見つけることです。
今回はmapですが、例えばglobal変数をテストで上書きしてたりとかしたなど、様々なケースで起きます。
Go 1.6の検知は?
先ほどのコードですが、ループ回数を増やすとGo自体も検知してくれます。
$ go run test.go 0 0 0 0 0 0 ~~~~~~ 0 0 0 0 0 0 0 0 fatal error: concurrent map read and map write goroutine 1 [running]: runtime.throw(0x1182e0, 0x21) /usr/local/opt/go/libexec/src/runtime/panic.go:530 +0x90 fp=0xc82003fe98 sp=0xc82003fe80 runtime.mapaccess1_fast64(0xb78a0, 0xc8200121b0, 0x9a, 0x1) /usr/local/opt/go/libexec/src/runtime/hashmap_fast.go:112 +0x5a fp=0xc82003feb8 sp=0xc82003fe98 main.main() /Users/xxxxx/test.go:13 +0xab fp=0xc82003ff50 sp=0xc82003feb8 runtime.main() /usr/local/opt/go/libexec/src/runtime/proc.go:188 +0x2b0 fp=0xc82003ffa0 sp=0xc82003ff50 runtime.goexit() /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc82003ffa8 sp=0xc82003ffa0 goroutine 5 [runnable]: main.main.func1(0xc8200121b0) /Users/xxxxx/test.go:9 +0x60 created by main.main /Users/xxxxx/test.go:11 +0x79 exit status 2
なぜ先ほどは検知できなかったのかと疑問に思うと思います。
というのもこのチェックはBest Effortでの検知なので、必ず検知するわけではなく「もしかしたら検知してくれる」くらいに考えておいた方が良いです。