ProgrammingGo開発者

Goにおけるmapの多スレッドアクセスおよびイテレーション中の要素削除に関する注意点は何ですか?

Hintsage AIアシスタントで面接を突破

答え。

Goでは、mapはデフォルトでスレッドセーフでないデータ構造です。異なるゴルーチンからmapに同時に書き込みや変更を行うと、レースコンディションや「concurrent map writes」というパニックが発生します。mapを保護するために通常はsync.Mutexsync.RWMutex、または標準ライブラリの特殊な型sync.Mapを使用して、安全な原子操作を実現します。

mapから要素を削除するにはdelete(map, key)を使用しますが、mapに対してrangeを使ったイテレーション中にはいくつかの注意点があります:

  • 現在のイテレーションの要素は安全に削除できます。
  • range中にmapに新しい要素を追加することはできますが、新しいキーが現在のイテレーションで処理されることは保証されません。
  • イテレーション中に他のゴルーチンからmapを変更することはエラーです(レースコンディションまたはパニック)。

スレッドセーフなmapの使用例:

type SafeMap struct { mu sync.RWMutex m map[string]int } func (s *SafeMap) Load(key string) (int, bool) { s.mu.RLock() v, ok := s.m[key] s.mu.RUnlock() return v, ok } func (s *SafeMap) Store(key string, value int) { s.mu.Lock() s.m[key] = value s.mu.Unlock() }

ひっかけ問題。

質問:イテレーション中にmapから要素を安全に削除できますか?

答え: はい、ただし、範囲イテレーションで取得されたキーを削除する場合のみです。しかし、他のゴルーチンからmapを並行して変更してはいけません:それはレースを引き起こします。

m := map[string]int{"a": 1, "b": 2, "c": 3} for k := range m { delete(m, k) // 安全!(このゴルーチンからのみ行う場合) }

このテーマの細かさを知らなかったために発生した実際のエラーの例。


物語

ある監視サービスがmapに関する統計を記録しており、複数のゴルーチンから同時に更新されていました(メトリックのカウンター)。ピーク時に「concurrent map writes」によるパニックが発生し、サービスが停止し、データが失われました。解決策:mutexを追加するか、通常のmapの代わりにsync.Mapを使用すること。


物語

データ移行中に、誰かが大きなmapのクリーンアップをスピードアップするために、各ゴルーチンが範囲を使って自分のキーの一部を削除しようとしました。その結果、常にデータレースと予測不可能なクラッシュが発生しました。移行後、逐次的なクリーンアップに戻るか、範囲の時間中にアクセスをブロックする必要がありました。


物語

mapに対するrangeループの中で、開発者は同じmapに新しいデータを追加しました(例えば、グラフの隣接ノードのリストを形成していました)。新しいキーが現在の範囲のイテレーションで無視される可能性があることが判明し、グラフの不完全な処理を引き起こしました。バグは珍しいケースを完全にテストするまで特定されませんでした。修正後、アルゴリズムは追加のキューを使用するように書き直されました。