Goにおける競合コレクションの作業は、多くのスレッドを必要とするアプリケーションに対する要求が高まる中で重要なテーマとなりました。Goの通常のmapはスレッドセーフではなく、データレースを引き起こす可能性があります。sync.Mapの登場は、外部の同期なしでコレクションへの安全な共同アクセスを提供する標準的な解決策を提供しました。
課題の歴史:
sync.Mapの出現前は、開発者は安全なアクセスを可能にするために、通常のmapと外部のMutexまたはRWMutexを使用する必要がありました。これにより、コードが複雑になり、同期のエラーの可能性が高まりました。Go 1.9では、競合コレクションの作業を簡素化することを目的としたsync.Mapが導入されました。
問題:
通常のmapはスレッドセーフではありません。複数のゴルーチンが同期なしにmapを読み書きすると、パニックや予期しない結果を引き起こします。Mutexの正しい使用は難しく、ロックやパフォーマンスの低下を引き起こす可能性があります。また、“ダブルチェック”や測定の難しい同期の扱いに関しても困難が生じます。
解決策:
sync.Mapは標準ライブラリから提供される特別な構造体であり、スレッドセーフなメソッドLoad、Store、LoadOrStore、Delete、Rangeを提供します。これは、部分的にロックフリーの戦略を実装しており、頻繁な読み取りと稀な書き込みのシナリオに最適化されています。
コード例:
import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("foo", 42) value, ok := m.Load("foo") fmt.Println(value, ok) // 42 true m.Delete("foo") }
重要な特徴:
マルチスレッドプログラムで全てのmapをsync.Mapに置き換えることはできますか?
いいえ、sync.Mapは通常のmapの普遍的な代替物ではありません。競合している独立した読み取りが多いデータ構造に適していますが、集中的な書き込み(頻繁な変更)や小さなコレクションでは、通常のmap + Mutexの方が速く効率的です。
複数のゴルーチンで通常のmapを読み取り専用として使用した場合はどうなりますか?
mapがすべて初期化され、その後すべてのゴルーチンが起動した後に変更されない場合、並行しての読み取りは許可され、安全です。しかし、データの削除や変更は予測できない動作、パニック、または壊れたmapを引き起こすことになります。
sync.Mapのキーとして使用できるデータ型は何ですか?
通常のmapと同じルールです:比較可能な型のみ(comparable types)。ただし、sync.Mapはインターフェース{}の任意の型のキーを受け入れるため、比較できない異なる意味のオブジェクトや、ランタイムエラーが発生する可能性があります。
コード例:
var m sync.Map m.Store([]int{1,2}, "value") // panic: runtime error: hash of unhashable type []int
開発者は、稀に変更されるアプリケーション設定を保存するためにsync.Mapを使用しましたが、その後ユーザーセッションのデータを書き込む量が急増し、GCの負荷が予想外に増加し、パフォーマンスが低下しました。
利点:
欠点:
チームは、高負荷サービスでよく要求される計算結果をキャッシュするためにsync.Mapを導入しました。「読み取り」の数は「書き込み」を数百倍上回ります。全てが安定して効率的に機能し、コードも短く保守しやすくなりました。
利点:
欠点: