概要
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での検知なので、必ず検知するわけではなく「もしかしたら検知してくれる」くらいに考えておいた方が良いです。