テック技報最新号へ →
コード

Go: map への同時書き込みは recover できない fatal error。Mutex で守り -race で確かめる

普通の map を複数のゴルーチンから同時に書くと、ランタイムが fatal error: concurrent map writes でプロセスごと落とすことがある。これは panic ではないので recover() では拾えない。

@amitsinha__07 · 投稿 2026-10-23
#go#concurrency#map#race

なぜ 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)。
  • レースディテクタは実行された経路しか見ないので、並行に動くテストを書いておくことが前提。

出典

X の元の投稿を開く