En Go, para la sincronización de gorutinas concurrentes, se utilizan estructuras del paquete sync, siendo las más comunes sync.Mutex, sync.RWMutex y sync.WaitGroup.
sync.Mutex proporciona mecanismos de exclusión mutua para el acceso a datos compartidos. Sus métodos son Lock() (bloquea) y Unlock() (desbloquea).
sync.RWMutex amplía las capacidades del mutex estándar: permite lecturas paralelas, pero modifica exclusivamente.
sync.WaitGroup está diseñado para esperar a que un grupo de gorutinas finalice. Con Add(int), Done() y Wait(), se gestiona el ciclo de vida del trabajo paralelo.
Por ejemplo:
var mu sync.RWMutex data := 0 // Lectura mu.RLock() fmt.Println(data) mu.RUnlock() // Escritura mu.Lock() data = 42 mu.Unlock()
Matices:
Unlock en un bloque defer para no olvidar desbloquear el mutex incluso en caso de pánico.WaitGroup.Done() corresponda a Add().¿Se puede capturar el mismo sync.Mutex (o RWMutex) dos veces consecutivas en la misma gorutina? ¿Qué sucederá?
Respuesta: No, si llamas a Lock() en el mismo Mutex dos veces seguidas sin un Unlock() intermedio, la gorutina se bloqueará para siempre (deadlock). En Go, los mutex no son recursivos.
Ejemplo:
var mu sync.Mutex mu.Lock() // ... mu.Lock() // DEADLOCK: se bloqueará para siempre, ya que el mismo hilo ya tiene el bloqueo
Historia
En un proyecto para una API REST de alta carga, un desarrollador encapsuló todo el procesamiento de la solicitud con un único mutex. Esto provocó una caída brusca del rendimiento: solo se podía procesar una solicitud a la vez, aunque se planeaba atender a miles de clientes. La razón fue el desconocimiento de la diferencia entre Mutex y RWMutex y la ignorancia de las lecturas paralelas.
Historia
Al copiar una estructura con Mutex en su interior por uno de los miembros del equipo, accidentalmente se pasó una copia a otra función. Esto llevó a un mensaje de pánico "sync: copy of sync.Mutex" y a caídas en producción bajo alta carga.
Historia
Al usar WaitGroup, olvidaron llamar a Done() en varias gorutinas, lo que provocó una espera infinita en Wait(), bloqueando el hilo principal. Como resultado, el servicio perdió disponibilidad hasta que se reinició manualmente.