W Go map z założenia nie jest bezpieczną dla wątków strukturą danych. Jednoczesne zapisywanie lub modyfikowanie mapy z różnych gorutyn prowadzi do warunków wyścigu i panik typu "concurrent map writes". Aby chronić mapę, zazwyczaj używa się sync.Mutex, sync.RWMutex lub specjalnego typu sync.Map z biblioteki standardowej, który realizuje bezpieczne operacje atomowe.
Usuwanie elementów z mapy można przeprowadzić przez delete(map, key), jednak podczas iteracji po mapie (przez range) wystąpią pewne niuanse:
range jest możliwe, ale nie ma gwarancji, że nowe klucze zostaną przetworzone w bieżącej iteracji.Przykład bezpiecznej pracy z 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() }
Pytanie: Czy można bezpiecznie usuwać elementy z mapy podczas iteracji range po tej samej mapie?
Odpowiedź: Tak, ale tylko jeśli usuwamy klucz, który już został wydany podczas iteracji range. Nie można jednocześnie modyfikować mapy z innych gorutyn, ponieważ prowadzi to do warunków wyścigu.
m := map[string]int{"a": 1, "b": 2, "c": 3} for k := range m { delete(m, k) // bezpieczne! (jeśli wykonujemy to tylko w tej gorutiny) }
Historia
Jedna z usług monitorujących prowadziła statystyki za pomocą mapy i była aktualizowana jednocześnie z wielu gorutyn (liczniki metryk). W szczytowym momencie wystąpiły paniki "concurrent map writes", usługa została wyłączona, dane utracono. Rozwiązanie: dodać mutex lub używać sync.Map zamiast zwykłej mapy.
Historia
W trakcie migracji danych ktoś postanowił przyspieszyć czyszczenie dużej mapy przy użyciu równoległych gorutyn, z których każda usuwała swoją część kluczy przez
range. W efekcie — ciągłe wyścigi danych i nieprzewidywalne awarie. Po migracji trzeba było wrócić do sekwencyjnego czyszczenia lub zablokować dostęp na czas trwaniarange.
Historia
W pętli
rangepo mapie deweloper dodawał nowe dane do tej samej mapy (na przykład tworzył listę sąsiednich węzłów dla grafu). Okazało się, że nowe klucze mogą zostać zignorowane w bieżącej iteracjirange, co prowadziło do niekompletnego przetwarzania grafu. Błąd został wykryty dopiero podczas pełnego testowania rzadkich przypadków. Po poprawce algorytm został przepisany z użyciem osobnej kolejki dodawania.