-race オプション
Go バイナリ実行時、 -race オプションを指定することで競合状態のテストを実施することができる。具体的には
go -race rungo -race build
のようにコード実行時、バイナリビルド時に指定できる。
ただし実行可能な環境は linux/amd64、freebsd/amd64、darwin/amd64、windows/amd64 のみ。
Example
Golang の map の非スレッドセーフ性と排他制御の記事で掲載した、map に対する並行アクセスを実行する。
main.go
package main func main() { kvs := NewKeyValueStore() for i := 0; i < 10; i++ { go func(kvs *KeyValueStore) { kvs.set("key", "value") kvs.get("key") }(kvs) } } type KeyValueStore struct { m map[string]string } func NewKeyValueStore() *KeyValueStore { return &KeyValueStore{m: make(map[string]string)} } func (s *KeyValueStore) set(k, v string) { s.m[k] = v } func (s *KeyValueStore) get(k string) (string, bool) { v, ok := s.m[k] return v, ok }
上記コードは map がスレッドセーフで無いために、値書き込み時のデータの競合状態が発生する。このコードを -race オプションとともに実行すると、競合状態が発生しうることに対する警告が吐き出される。
実行結果
$ go run -race main.go
==================
WARNING: DATA RACE
Write at 0x00c000088000 by goroutine 7:
runtime.mapassign_faststr()
/usr/local/go/src/runtime/map_faststr.go:202 +0x0
main.main.func1()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x71
Previous write at 0x00c000088000 by goroutine 6:
runtime.mapassign_faststr()
/usr/local/go/src/runtime/map_faststr.go:202 +0x0
main.main.func1()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x71
Goroutine 7 (running) created at:
main.main()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c
Goroutine 6 (finished) created at:
main.main()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c
==================
==================
WARNING: DATA RACE
Write at 0x00c00008c088 by goroutine 7:
main.main.func1()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x86
Previous write at 0x00c00008c088 by goroutine 6:
main.main.func1()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:25 +0x86
Goroutine 7 (running) created at:
main.main()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c
Goroutine 6 (finished) created at:
main.main()
/Users/rennnosuke/go/src/github.com/rennnosuke/rens-blog-codes/20200323/not_concurrent_safe/main.go:8 +0x9c
==================
Found 2 data race(s)
exit status 66
競合が発生しうる場合、上記のように競合が発生するコード上の箇所が表示される。競合検出は実行された処理のみに対して実施され、実行されない処理に競合の可能性があっても検出はされない。
競合状態にならない場合、特に出力はなく実行は終了する。
main.go
package main import ( "fmt" "sync" ) func main() { kvs := NewConcurrentKeyValueStore() for i := 0; i < 10; i++ { go func(kvs *ConcurrentKeyValueStore) { kvs.set("key", "value") kvs.get("key") }(kvs) } } type ConcurrentKeyValueStore struct { m map[string]string mu sync.RWMutex } func NewConcurrentKeyValueStore() *ConcurrentKeyValueStore { return &ConcurrentKeyValueStore{m: make(map[string]string)} } func (s *ConcurrentKeyValueStore) set(k, v string) { s.mu.Lock() defer s.mu.Unlock() s.m[k] = v } func (s *ConcurrentKeyValueStore) get(k string) (string, bool) { s.mu.RLock() defer s.mu.RUnlock() v, ok := s.m[k] return v, ok }
実行結果
$ go run -race main.go // 何も出力されない
パッケージインストール時の競合検出
コード実行・ビルド時だけでなく、外部パッケージをインストールするときにも競合検出を実施することができる。
go get -race [package]go install -race [package]