Sobes.tech
Junior

Cos'è un deadlock quando si lavora con goroutine?

sobes.tech AI

Risposta dell'AI

Il deadlock (impasse) è una situazione in cui due o più goroutine si bloccano a vicenda aspettando l'una l'altra, e nessuna può continuare l'esecuzione. Si verifica più frequentemente quando si lavora con mutex o canali.

Esempi di deadlock:

  1. Blocco di mutex in ordine diverso:

    // Esempio di deadlock con mutex
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Blocca mu1
        time.Sleep(100 * time.Millisecond) // Attende un po'
        mu2.Lock() // Tenta di bloccare mu2 (occupato da goroutine2)
        fmt.Println("goroutine1 ha acquisito mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Blocca mu2
        time.Sleep(100 * time.Millisecond) // Attende un po'
        mu1.Lock() // Tenta di bloccare mu1 (occupato da goroutine1)
        fmt.Println("goroutine2 ha acquisito mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 ha bloccato mu1 e aspetta mu2. Goroutine2 ha bloccato mu2 e aspetta mu1. Entrambe le goroutine sono bloccate.

  2. Invio in canale senza buffer senza ricevitore:

    // Esempio di deadlock con canale
    func main() {
        ch := make(chan int)
        //go func() { // Se non si avvia una goroutine di lettura, si verifica deadlock
        //	<-ch
        //}()
        ch <- 1 // Invio nel canale senza ricevitore attivo
        fmt.Println("Inviato al canale") // Questa riga non sarà raggiunta
    }
    

    L'invio in un canale senza buffer si blocca finché un'altra goroutine non inizia a leggere da esso. Se non c'è ricevitore, si verifica deadlock.

  3. Lettura da canale senza mittente (dopo chiusura o con buffer vuoto):

    // Esempio di deadlock durante la lettura da canale
    func main() {
        ch := make(chan int, 1) // Canale con buffer di 1 elemento
        ch <- 1 // Invia
        close(ch) // Chiude il canale
        <-ch // Legge 1
        <-ch // Tentativo di leggere di nuovo da un canale chiuso e vuoto - deadlock
    }
    

    La lettura da un canale chiuso e non vuoto termina con successo. La lettura da un canale chiuso e vuoto o da un canale aperto ma vuoto senza mittente provoca blocco.

Il runtime di Golang può rilevare alcuni tipi di deadlock (ad esempio, quando tutte le goroutine bloccate aspettano di ricevere o inviare a una singola goroutine), e in tal caso il programma termina bruscamente con il messaggio "fatal error: all goroutines are asleep - deadlock!". Tuttavia, non tutti i deadlock possono essere rilevati automaticamente.

Per prevenire deadlock, è importante:

  • Mantenere un ordine coerente nell'acquisizione dei mutex.
  • Assicurarsi che ogni invio in un canale senza buffer abbia una corrispondente operazione di lettura, e viceversa.
  • Utilizzare select con default o timeout quando si lavora con canali, se necessario.
  • Gestire attentamente il ciclo di vita delle goroutine e la chiusura dei canali.