Sobes.tech
Junior

¿Qué es un deadlock al trabajar con goroutine?

sobes.tech AI

Respuesta de la IA

El bloqueo (deadlock) es una situación en la que dos o más goroutines se bloquean mutuamente esperando unas a otras, y ninguna puede continuar su ejecución. Ocurre con mayor frecuencia al trabajar con mutexes o canales.

Ejemplos de deadlock:

  1. Bloqueo de mutexes en orden diferente:

    // Ejemplo de deadlock con mutexes
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Bloquea mu1
        time.Sleep(100 * time.Millisecond) // Espera un poco
        mu2.Lock() // Intenta bloquear mu2 (ocupado por goroutine2)
        fmt.Println("goroutine1 adquirió mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Bloquea mu2
        time.Sleep(100 * time.Millisecond) // Espera un poco
        mu1.Lock() // Intenta bloquear mu1 (ocupado por goroutine1)
        fmt.Println("goroutine2 adquirió mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 bloqueó mu1 y espera mu2. Goroutine2 bloqueó mu2 y espera mu1. Ambas goroutines están bloqueadas.

  2. Enviar a un canal sin buffer sin receptor:

    // Ejemplo de deadlock con canal
    func main() {
        ch := make(chan int)
        //go func() { // Si no se inicia una goroutine para leer, habrá deadlock
        //	<-ch
        //}()
        ch <- 1 // Envío al canal sin receptor activo
        fmt.Println("Enviado al canal") // Esta línea no se alcanzará
    }
    

    El envío a un canal sin buffer se bloquea hasta que otra goroutine comience a leer de él. Si no hay receptor, se produce un deadlock.

  3. Lectura de un canal sin remitente (después de cerrar o con buffer vacío):

    // Ejemplo de deadlock al leer de un canal
    func main() {
        ch := make(chan int, 1) // Canal con buffer de 1 elemento
        ch <- 1 // Enviamos 1
        close(ch) // Cerramos el canal
        <-ch // Leemos 1
        <-ch // Intentamos leer de nuevo de un canal cerrado y vacío - deadlock
    }
    

    La lectura de un canal cerrado y no vacío terminará con éxito. La lectura de un canal cerrado y vacío o de un canal abierto pero vacío sin remitente provocará bloqueo.

El runtime de Golang puede detectar ciertos tipos de deadlocks (por ejemplo, cuando todas las goroutines bloqueadas esperan recibir o enviar a una única goroutine), y en ese caso, el programa terminará abruptamente con el mensaje "fatal error: all goroutines are asleep - deadlock!". Sin embargo, no todos los deadlocks pueden ser detectados automáticamente.

Para prevenir deadlocks, es importante:

  • Mantener un orden consistente en la adquisición de mutexes.
  • Asegurarse de que cada envío a un canal sin buffer tenga una lectura correspondiente, y viceversa.
  • Utilizar select con la rama default o timeouts al trabajar con canales cuando sea necesario.
  • Gestionar cuidadosamente el tiempo de vida de las goroutines y el cierre de canales.