Go: map への同時書き込みは recover できない fatal error。Mutex で守り -race で確かめる
普通の map を複数のゴルーチンから同時に書くと、ランタイムが fatal error: concurrent map writes でプロセスごと落とすことがある。これは panic ではないので recover() では拾えない。
なぜ recover できないか
ランタイムは map の内部状態が壊れたことを検出すると、続行は危険と判断して fatal error で即終了する。panic と違って defer + recover() の対象外なので、「HTTP ハンドラごとに recover ミドルウェアを入れているから大丈夫」は通用しない。しかも検出は「ベストエフォート」なので、テストでは落ちず本番の負荷でだけ落ちる、ということが起きる。
対処
go
type Cache struct {
mu sync.RWMutex
m map[string]Item
}
func (c *Cache) Get(k string) (Item, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.m[k]
return v, ok
}
func (c *Cache) Set(k string, v Item) {
c.mu.Lock()
defer c.mu.Unlock()
c.m[k] = v
}- 読み書きが混ざるなら
sync.RWMutex、書き込みが多いならsync.Mutex。 - キーが増える一方で、ゴルーチンごとに別のキーを触るような用途なら
sync.Mapも選択肢(一般用途では Mutex + map のほうが速くて型安全なことが多い)。 - テストと CI で
go test -race ./...を回す。データ競合はレースディテクタで実際に起きた箇所が報告される。
注意点
- 「読むだけのゴルーチン」と「書くゴルーチン」が同時に動くのもダメ(
concurrent map read and map write)。 - レースディテクタは実行された経路しか見ないので、並行に動くテストを書いておくことが前提。
出典
- @amitsinha__07 · 2026-10-23
- Go FAQ「Why are map operations not defined to be atomic?」: go.dev/doc/faq#atomic_maps