Sobes.tech
Junior

O que é um deadlock ao trabalhar com goroutine?

sobes.tech IA

Resposta da IA

Um deadlock (impasse) é uma situação em que duas ou mais goroutines bloqueiam-se mutuamente esperando umas pelas outras, e nenhuma delas consegue continuar a execução. Geralmente ocorre ao trabalhar com mutexes ou canais.

Exemplos de deadlock:

  1. Bloqueio de mutexes em ordens diferentes:

    // Exemplo de deadlock com mutexes
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Bloqueia mu1
        time.Sleep(100 * time.Millisecond) // Espera um pouco
        mu2.Lock() // Tenta bloquear mu2 (ocupado pela goroutine2)
        fmt.Println("goroutine1 adquiriu mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Bloqueia mu2
        time.Sleep(100 * time.Millisecond) // Espera um pouco
        mu1.Lock() // Tenta bloquear mu1 (ocupado pela goroutine1)
        fmt.Println("goroutine2 adquiriu mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 bloqueou mu1 e espera mu2. Goroutine2 bloqueou mu2 e espera mu1. Ambas goroutines estão bloqueadas.

  2. Envio para canal sem buffer sem receptor:

    // Exemplo de deadlock com canal
    func main() {
        ch := make(chan int)
        //go func() { // Se não iniciar uma goroutine para leitura, haverá deadlock
        //	<-ch
        //}()
        ch <- 1 // Envio para o canal sem receptor ativo
        fmt.Println("Enviado para o canal") // Esta linha não será atingida
    }
    

    O envio para um canal sem buffer bloqueia até que outra goroutine comece a ler dele. Se não houver receptor, ocorre deadlock.

  3. Leitura de um canal sem remetente (após fechamento ou com buffer vazio):

    // Exemplo de deadlock ao ler de um canal
    func main() {
        ch := make(chan int, 1) // Canal com buffer de 1 elemento
        ch <- 1 // Enviou 1
        close(ch) // Fechou o canal
        <-ch // Leu 1
        <-ch // Tenta ler novamente de um canal fechado e vazio - deadlock
    }
    

    A leitura de um canal fechado e não vazio termina com sucesso. A leitura de um canal fechado e vazio ou de um canal aberto mas vazio sem remetente causará bloqueio.

O runtime do Golang pode detectar certos tipos de deadlocks (por exemplo, quando todas as goroutines bloqueadas esperam receber ou enviar para uma única goroutine), e nesse caso, o programa terminará abruptamente com a mensagem "fatal error: all goroutines are asleep - deadlock!". No entanto, nem todos os deadlocks podem ser detectados automaticamente.

Para evitar deadlocks, é importante:

  • Manter uma ordem consistente na aquisição de mutexes.
  • Garantir que cada envio para um canal sem buffer tenha uma leitura correspondente, e vice-versa.
  • Utilizar select com default ou timeouts ao trabalhar com canais, quando necessário.
  • Gerenciar cuidadosamente o tempo de vida das goroutines e o fechamento dos canais.