In Go ist map standardmäßig nicht thread-sicher. Gleichzeitige Schreib- oder Änderungsoperationen an einer map aus verschiedenen Goroutinen führen zu Datenrennen und Paniken wie "concurrent map writes". Zum Schutz der map verwendet man normalerweise sync.Mutex, sync.RWMutex oder den speziellen Typ sync.Map aus der Standardbibliothek, der sichere atomare Operationen implementiert.
Elemente aus der map können über delete(map, key) entfernt werden, jedoch gibt es beim Iterieren über die map (mit range) einige Nuancen:
Beispiel einer thread-sicheren Arbeit mit 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() }
Frage: Kann man sicher Elemente aus einer map während einer range-Iteration über dieselbe map entfernen?
Antwort: Ja, nur wenn man den Schlüssel entfernt, der bereits von der range-Iteration ausgegeben wurde. Aber man darf dabei die map nicht gleichzeitig aus anderen Goroutinen modifizieren: Das würde zu einem Rennen führen.
m := map[string]int{"a": 1, "b": 2, "c": 3} for k := range m { delete(m, k) // sicher! (wenn nur aus dieser Goroutine) }
Geschichte
Ein Überwachungsdienst führte Statistiken über eine map und wurde gleichzeitig von mehreren Goroutinen aktualisiert (Metrikzähler). In Spitzenzeiten traten Paniken "concurrent map writes" auf, der Dienst fiel aus und Daten gingen verloren. Lösung: Fügen Sie einen Mutex hinzu oder verwenden Sie sync.Map anstelle einer normalen map.
Geschichte
Im Zuge der Datenmigration beschloss jemand, die Bereinigung einer großen map mit parallelen Goroutinen zu beschleunigen, von denen jede ihren Teil von Schlüsseln über range entfernte. Das Ergebnis waren ständige Datenrennen und unvorhersehbare Abstürze. Nach der Migration musste wieder zur sequentiellen Bereinigung zurückgekehrt werden oder der Zugriff während des range blockiert werden.
Geschichte
Im range-Zyklus über eine map fügte der Entwickler neue Daten in dieselbe map hinzu (zum Beispiel eine Liste benachbarter Knoten für einen Graphen zu erstellen). Es stellte sich heraus, dass neue Schlüssel im aktuellen Durchlauf des range ignoriert werden könnten, was zu einer unvollständigen Verarbeitung des Graphen führte. Der Fehler wurde erst bei vollständigen Tests seltener Fälle entdeckt. Nach der Behebung wurde der Algorithmus überarbeitet, um eine separate Warteschlange für Hinzufügungen zu verwenden.