En Go, map por defecto no es seguro para subprocesos. La escritura simultánea o la modificación de un map desde diferentes gorutinas conduce a condiciones de carrera y pánicos del tipo "concurrent map writes". Para proteger el map, se suelen utilizar sync.Mutex, sync.RWMutex o el tipo especial sync.Map de la biblioteca estándar, que implementa operaciones atómicas seguras.
Los elementos se pueden eliminar del map utilizando delete(map, key), sin embargo, durante la iteración sobre el map (a través de range) surgen matices:
Ejemplo de trabajo seguro con 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() }
Pregunta: ¿Se pueden eliminar elementos de forma segura de un map durante una iteración range sobre ese mismo map?
Respuesta: Sí, solo si se elimina la clave que ya ha sido procesada en la iteración range. Pero no se puede modificar el map desde otras gorutinas al mismo tiempo: esto causará una carrera.
m := map[string]int{"a": 1, "b": 2, "c": 3} for k := range m { delete(m, k) // ¡seguro! (si se hace solo desde esta gorutina) }
Historia
Uno de los servicios de monitoreo mantenía estadísticas sobre un map y se actualizaba inmediatamente desde varias gorutinas (contadores de métricas). En el momento pico, ocurrieron pánicos "concurrent map writes", el servicio se desconectaba y se perdían datos. Solución: agregar un mutex o usar sync.Map en lugar de un map común.
Historia
Durante la migración de datos, alguien decidió acelerar la limpieza de un gran map utilizando gorutinas paralelas, cada una de las cuales eliminaba su parte de claves a través de range. Como resultado, se produjeron constantes carreras de datos y bloqueos impredecibles. Después de la migración, fue necesario regresar a una limpieza secuencial o bloquear el acceso durante el range.
Historia
En un ciclo range sobre un map, el desarrollador añadía nuevos datos al mismo map (por ejemplo, generando una lista de nodos adyacentes para un grafo). Resultó que las nuevas claves podían ser ignoradas en la iteración actual de range, lo que llevó a un procesamiento incompleto del grafo. El error solo fue descubierto durante la prueba exhaustiva de casos raros. Después de la corrección, el algoritmo fue reescrito utilizando una cola separada para las adiciones.