Programmingバックエンド開発者

Goでのマップを操作する際のデータ競合防止メカニズムについて説明してください。どのような保証があり、保護なしで複数のゴルーチンでマップを操作するとエラーが発生するのはなぜですか?

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

回答

Goではマップはデフォルトでスレッドセーフではありません。複数のゴルーチンが、同期なしで同じマップを同時に読み書きすると、データ競合が発生し、fatal error: concurrent map read and map writeというパニックやデータの破損が引き起こされます。

マップをスレッドセーフに操作するためには、以下が必要です:

  • すべての読み書き操作を保護するためにsync.Mutexまたはsync.RWMutexを使用すること。
  • または、高負荷の競合シナリオ向けに設計されたsync.Mapパッケージを使用すること。
var mu sync.RWMutex m := make(map[string]int) // 書き込み mu.Lock() m["key"] = 1 mu.Unlock() // 読み取り mu.RLock() v := m["key"] mu.RUnlock() // またはsync.Map var sm sync.Map sm.Store("key", 1) v, _ := sm.Load("key")

ひっかけ質問

なぜGoのマップで異なるゴルーチンから同時に読み書きを行うことがそんなに危険なのか、マップは組み込み型なのに?

回答:Goの組み込み型であるマップは同期を提供しません。複数のゴルーチンからマップに同時にアクセスすると、単に不正確な値が返るだけでなく、プログラムがクラッシュする可能性があります。たとえ交差するキーがない場合でも、同時に読み書きすると致命的なエラーが発生することがあります。これは、他のいくつかの言語のコレクションが競合するアクセスに「耐性」を持つのとは異なります。

テーマの細かい部分を知らなかったために発生した実際のエラーの例


事例

実際のプロジェクトで、開発者はデータキャッシングのためにグローバルマップを使用しました。サービスはテストでは安定して動作しましたが、負荷テストや本番環境でfatal error: concurrent map read and map writeのエラーでクラッシュし始めました。原因は、Mutexを使用せずに異なるHTTPリクエストからマップに並行アクセスしていたことです。


事例

Goのウェブアプリケーションで、一人のプログラマーがパフォーマンスを向上させるために通常のマップを接続プールとして使用し、マルチスレッドはフレームワークによって保証されていると考えました。トラフィックが急増すると、サービスは明確な理由もなくクラッシュし始めました:競合によりマップ内のデータが破損し、パニックが発生しました。


事例

社内のGoサービスでは、アプリケーションが「リアルタイム」で統計を収集するためにマップを使用し、主なアクセスは書き込みのみであると考えていました。実際には、コードの別の部分が定期的にデータを要求し、レポートを生成していたため、統計が作動するタイミングで難解なクラッシュが発生しました。分析の結果、読み書きがロックなしで重なっていることが分かりました。