In Go is een map standaard geen thread-safe datastructuur. Gelijktijdige schrijf- of wijzigingsacties op een map vanuit verschillende goroutines leiden tot races en panieksituaties van het type "concurrent map writes". Om een map te beschermen, gebruikt men meestal sync.Mutex, sync.RWMutex of het speciale type sync.Map uit de standaardbibliotheek, dat veilige atomische operaties implementeert.
Elementen kunnen worden verwijderd uit een map met delete(map, key), maar er zijn enkele nuances tijdens het itereren door de map (via range):
Voorbeeld van thread-safe werken met 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() }
Vraag: Is het veilig om elementen uit een map te verwijderen tijdens een range-iteratie over dezelfde map?
Antwoord: Ja, alleen als je de sleutel verwijdert die al door de range-iteratie is uitgegeven. Maar je mag de map niet tegelijkertijd vanuit andere goroutines aanpassen: dat leidt tot een race.
m := map[string]int{"a": 1, "b": 2, "c": 3} for k := range m { delete(m, k) // veilig! (als dit alleen uit deze goroutine wordt gedaan) }
Verhaal
Een van de monitoringsservices hield statistieken bij via een map en werd direct uit meerdere goroutines bijgewerkt (metriekentellers). Op piekmomenten ontstonden panieksituaties van "concurrent map writes", waardoor de service werd uitgeschakeld en gegevens verloren gingen. Oplossing: voeg mutex toe of gebruik sync.Map in plaats van een gewone map.
Verhaal
Bij het migreren van gegevens besloot iemand de opruiming van een grote map te versnellen met behulp van parallelle goroutines, waarvan elke één deel van de sleutels via range verwijderde. Het resultaat was voortdurende data races en onvoorspelbare crashes. Na de migratie moest men terugkeren naar seriële opruiming of de toegang blokkeren tijdens de range.
Verhaal
In een range-lus over een map voegde de ontwikkelaar nieuwe gegevens toe aan dezelfde map (bijvoorbeeld, het vormde een lijst van aangrenzende knooppunten voor een grafiek). Het bleek dat nieuwe sleutels konden worden genegeerd in de huidige iteratie van range, wat leidde tot onvolledige verwerking van de grafiek. De bug werd pas ontdekt bij grondige tests van zeldzame gevallen. Na de correctie werd het algoritme herschreven met een aparte rij voor toevoegingen.