Golangの並列実行時の競合状態検出
golang
Lastmod: 2020-10-19

-race オプション

Go バイナリ実行時、 -race オプションを指定することで競合状態のテストを実施することができる。具体的には

  • go -race run
  • go -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]

参考文献