ProgrammierungGo-Entwickler

Welche Feinheiten bei der Arbeit mit map in Go sollten Sie beachten, um implizite Fehler bei gleichzeitiger Zugriff und dem Entfernen von Elementen während der Iteration zu vermeiden?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort.

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:

  • Es ist sicher, das Element der aktuellen Iteration zu entfernen.
  • Neue Elemente in die map während des range hinzuzufügen ist möglich, aber es ist nicht garantiert, dass die neuen Schlüssel in der aktuellen Iteration behandelt werden.
  • Die Änderung der map aus einer anderen Goroutine während der Iteration ist ein Fehler (Datenrennen oder panic).

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() }

Fangfrage.

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) }

Beispiele für echte Fehler aufgrund unbekannter Feinheiten des Themas.


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.