レースコンディションとは
複数のGoroutineが同じメモリ領域に同時にアクセスし、少なくとも1つが書き込みを行う場合に発生。実行タイミングによって結果が変わる不確定な動作を引き起こす。
発生する条件
- 複数のGoroutineが存在
- 同じメモリ領域にアクセス
- 少なくとも1つが書き込み操作
典型的なパターン
// カウンタの更新
var counter int
counter++ // READ → COMPUTE → WRITE の3ステップで競合
// マップの同時読み書き
var cache = make(map[string]string)
cache[key] = value // Goのマップは並行アクセス安全ではない
// スライスの同時追加
results = append(results, item) // 内部配列再確保時に競合
// 構造体フィールドの更新
type Counter struct { count int }
c.count++ // フィールドアクセスも競合の対象
防止方法
- Mutex (排他制御)
var mu sync.Mutex
mu.Lock()
// クリティカルセクション
mu.Unlock()
- RWMutex (読み書き分離)
mu.RLock() // 読み取り専用ロック
mu.RUnlock()
mu.Lock() // 書き込みロック
mu.Unlock()
- Atomic操作
atomic.AddInt64(&counter, 1)
- Channel (通信で共有)
results := make(chan string)
go func() { results <- "data" }()
- sync.Map (並行安全なマップ)
var m sync.Map
m.Store(key, value)
検出方法
go test -race
go build -race
go run -race main.go
Node.jsとの違い
Node.jsはシングルスレッドのため、基本的にレースコンディションは発生しない(Worker Threads使用時は注意)。Goは真の並列実行のため、共有データへのアクセスには常に注意が必要。