Sobes.tech
Junior

Ce este un deadlock atunci când lucrezi cu goroutine?

sobes.tech AI

Răspuns de la AI

Blocajul (deadlock) este o situație în care două sau mai multe goroutines se blochează reciproc așteptând una pe cealaltă, și niciuna dintre ele nu poate continua execuția. Apare cel mai des atunci când se lucrează cu mutex-uri sau canale.

Exemple de deadlock-uri:

  1. Blocarea mutex-urilor în ordine diferită:

    // Exemplu de deadlock cu mutex-uri
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Blochează mu1
        time.Sleep(100 * time.Millisecond) // Așteaptă puțin
        mu2.Lock() // Încearcă să blocheze mu2 (ocupat de goroutine2)
        fmt.Println("goroutine1 a dobândit mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Blochează mu2
        time.Sleep(100 * time.Millisecond) // Așteaptă puțin
        mu1.Lock() // Încearcă să blocheze mu1 (ocupat de goroutine1)
        fmt.Println("goroutine2 a dobândit mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 a blocat mu1 și așteaptă mu2. Goroutine2 a blocat mu2 și așteaptă mu1. Ambele goroutines sunt blocate.

  2. Trimitere într-un canal fără buffer fără receptor:

    // Exemplu de deadlock cu canal
    func main() {
        ch := make(chan int)
        //go func() { // Dacă nu se pornește o goroutine pentru citire, va apărea deadlock
        //	<-ch
        //}()
        ch <- 1 // Trimitere în canal fără receptor activ
        fmt.Println("Trimis în canal") // Această linie nu va fi atinsă
    }
    

    Trimiterea într-un canal fără buffer se blochează până când o altă goroutine începe să citească din el. Dacă nu există receptor, apare deadlock.

  3. Citirea dintr-un canal fără emițător (după închidere sau cu buffer gol):

    // Exemplu de deadlock la citirea dintr-un canal
    func main() {
        ch := make(chan int, 1) // Canal bufferizat cu 1 element
        ch <- 1 // Trimis
        close(ch) // Închide canalul
        <-ch // Citește 1
        <-ch // Încearcă să citească din nou dintr-un canal închis și gol - deadlock
    }
    

    Citirea dintr-un canal închis și nevid se va termina cu succes. Citirea dintr-un canal închis și gol sau dintr-un canal deschis, dar gol, fără emițător va duce la blocare.

Runtime-ul Golang poate detecta anumite tipuri de deadlock-uri (de exemplu, când toate goroutine-urile blocate așteaptă să primească sau să trimită către o singură goroutine), și în acest caz programul se va încheia brusc cu mesajul "fatal error: all goroutines are asleep - deadlock!". Totuși, nu toate deadlock-urile pot fi detectate automat.

Pentru a preveni deadlock-urile, este important:

  • Să păstrezi o ordine consecventă în capturarea mutex-urilor.
  • Să te asiguri că pentru fiecare trimitere într-un canal fără buffer există o citire corespunzătoare, și invers.
  • Să folosești select cu default sau timeout-uri atunci când lucrezi cu canale, dacă este necesar.
  • Să gestionezi cu grijă durata de viață a goroutine-urilor și închiderea canalelor.