En Go, map par défaut n'est pas une structure de données thread-safe. L'écriture ou la modification simultanée d'une map par différentes goroutines entraîne des conditions de concurrence et des paniques de type "concurrent map writes". Pour protéger une map, on utilise généralement sync.Mutex, sync.RWMutex ou le type spécial sync.Map de la bibliothèque standard, qui implémente des opérations atomiques sûres.
On peut supprimer des éléments d'une map via delete(map, key), mais lors de l'itération sur la map (avec range), il y a des nuances à prendre en compte :
Exemple de travail en toute sécurité avec 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() }
Question : Peut-on supprimer en toute sécurité des éléments d'une map pendant une itération range sur cette même map ?
Réponse : Oui, uniquement si l'on supprime la clé qui a déjà été retournée par l'itération range. Mais il ne faut pas modifier la map depuis d'autres goroutines en parallèle : cela entraînerait une condition de concurrence.
m := map[string]int{"a": 1, "b": 2, "c": 3} for k := range m { delete(m, k) // sûr ! (si fait uniquement depuis cette goroutine) }
Histoire
Un des services de surveillance tenait des statistiques sur une map et se mettait à jour instantanément depuis plusieurs goroutines (compteurs de métriques). À un moment de pointe, des paniques "concurrent map writes" sont survenues, le service s'est arrêté et les données ont été perdues. Solution : ajouter un mutex ou utiliser sync.Map au lieu d'une map ordinaire.
Histoire
Lors de la migration des données, quelqu'un a décidé d'accélérer le nettoyage d'une grande map à l'aide de goroutines parallèles, chacune supprimant sa partie de clés via range. Au final, des data races constantes et des plantages imprévisibles. Après la migration, il a fallu revenir à un nettoyage séquentiel ou bloquer l'accès pendant un range.
Histoire
Dans une boucle range sur une map, un développeur ajoutait de nouvelles données dans la même map (par exemple, il formait une liste de nœuds adjacents pour un graphique). Il s'est avéré que les nouvelles clés pouvaient être ignorées dans l'itération range actuelle, ce qui a conduit à un traitement incomplet du graphique. Le bug n'a été découvert qu'à l'issue de tests complets de cas rares. Après correction, l'algorithme a été réécrit en utilisant une file d'attente séparée pour les ajouts.