Sobes.tech
Junior

Čo je deadlock pri práci s goroutine?

sobes.tech AI

Odpoveď od AI

Deadlock (zaseknutie) je situácia, keď dve alebo viac gorutín blokujú jedna druhú, čakajúc na seba, a žiadna z nich nemôže pokračovať v vykonávaní. Najčastejšie k tomu dochádza pri práci s mutexmi alebo kanálmi.

Príklady deadlocku:

  1. Zámky mutexov v rôznom poradí:

    // Príklad deadlocku s mutexmi
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Získa mu1
        time.Sleep(100 * time.Millisecond) // Čaká trochu
        mu2.Lock() // Pokúša sa získať mu2 (ktorý drží goroutine2)
        fmt.Println("goroutine1 získal mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Získa mu2
        time.Sleep(100 * time.Millisecond) // Čaká trochu
        mu1.Lock() // Pokúša sa získať mu1 (ktorý drží goroutine1)
        fmt.Println("goroutine2 získal mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 získala mu1 a čaká na mu2. Goroutine2 získala mu2 a čaká na mu1. Obidve gorutiny sú zablokované.

  2. Odoslanie do nebufferovaného kanála bez príjemcu:

    // Príklad deadlocku s kanálom
    func main() {
        ch := make(chan int)
        //go func() { // Ak nespustíme gorutinu na čítanie, dôjde k deadlocku
        //	<-ch
        //}()
        ch <- 1 // Odoslanie do kanála bez aktívneho príjemcu
        fmt.Println("Odoslané do kanála") // Táto riadok nebude dosiahnutý
    }
    

    Odoslanie do nebufferovaného kanála sa zablokuje, kým iná gorutina nezačne čítať z neho. Ak nie je žiadny príjemca, dôjde k deadlocku.

  3. Čítanie z kanála bez odosielateľa (po zatvorení alebo prázdnom bufferovanom):

    // Príklad deadlocku pri čítaní z kanála
    func main() {
        ch := make(chan int, 1) // Bufferovaný kanál na 1 prvok
        ch <- 1 // Odoslané 1
        close(ch) // Zatvorili sme kanál
        <-ch // Prečítali 1
        <-ch // Pokúšame sa čítať znova zo zatvoreného a prázdneho kanála - deadlock
    }
    

    Čítanie zo zatvoreného, neplneného kanála bude úspešné. Čítanie zo zatvoreného a prázdneho alebo z otvoreného, ale prázdneho nebufferovaného kanála bez odosielateľa spôsobí blokovanie.

Golang runtime môže detekovať určité typy deadlockov (napríklad, keď všetky zablokované gorutiny čakajú na prijatie alebo odoslanie od jednej gorutiny), a v takom prípade program neočakávane skončí s hlásením "fatal error: all goroutines are asleep - deadlock!". Avšak, nie všetky deadlocky môžu byť automaticky detekované.

Na prevenciu deadlockov je dôležité:

  • Dodržiavať konzistentný poradie získavania mutexov.
  • Zabezpečiť, že pre každé odoslanie do nebufferovaného kanála existuje zodpovedajúce čítanie, a naopak.
  • Pri práci s kanálmi používať select s default vetvou alebo časovými limitmi podľa potreby.
  • Dôsledne riadiť životnosť gorutín a zatváranie kanálov.