Sobes.tech
Junior

Šta je deadlock pri radu sa goroutine?

sobes.tech АИ

Одговор од АИ

Deadlock (zastojevanje) je situacija kada dve ili više gorutina blokiraju jedna drugu, čekajući jedna drugu, i nijedna od njih ne može da nastavi sa izvršavanjem. Najčešće se javlja pri radu sa mutexima ili kanalima.

Primeri deadlock-a:

  1. Blokiranje mutexa u različitom redosledu:

    // Primer deadlock-a sa mutexima
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Zauzima mu1
        time.Sleep(100 * time.Millisecond) // Čeka malo
        mu2.Lock() // Pokušava da zauzme mu2 (koji drži goroutine2)
        fmt.Println("goroutine1 zauzela mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Zauzima mu2
        time.Sleep(100 * time.Millisecond) // Čeka malo
        mu1.Lock() // Pokušava da zauzme mu1 (koji drži goroutine1)
        fmt.Println("goroutine2 zauzela mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 je zauzela mu1 i čeka mu2. Goroutine2 je zauzela mu2 i čeka mu1. Obje gorutine su blokirane.

  2. Slanje u nebuferisani kanal bez primaoca:

    // Primer deadlock-a sa kanalom
    func main() {
        ch := make(chan int)
        //go func() { // Ako ne pokrenemo gorutinu za čitanje, doći će do deadlock-a
        //	<-ch
        //}()
        ch <- 1 // Slanje u kanal bez aktivnog primaoca
        fmt.Println("Poslato u kanal") // Ova linija neće biti dostignuta
    }
    

    Slanje u nebuferisani kanal blokira dok druga gorutina ne počne da čita iz njega. Ako nema primaoca, javlja se deadlock.

  3. Čitanje iz kanala bez pošiljaoca (nakon zatvaranja ili praznog bufferovanog):

    // Primer deadlock-a pri čitanju iz kanala
    func main() {
        ch := make(chan int, 1) // Bufferovani kanal na 1 element
        ch <- 1 // Poslali smo 1
        close(ch) // Zatvorili kanal
        <-ch // Pročitali 1
        <-ch // Pokušavamo ponovo da čitamo iz zatvorenog i praznog kanala - deadlock
    }
    

    Čitanje iz zatvorenog, nepopunjenog kanala će biti uspešno. Čitanje iz zatvorenog i praznog ili iz otvorenog, ali praznog nebuferisanog kanala bez pošiljaoca dovodi do blokade.

Golang runtime može da otkrije određene tipove deadlock-ova (na primer, kada sve blokirane gorutine čekaju na primanje ili slanje od strane jedne gorutine), i u takvom slučaju program će neočekivano prekinuti sa porukom "fatal error: all goroutines are asleep - deadlock!". Međutim, nisu svi deadlock-ovi automatski detektovani.

Za sprečavanje deadlock-ova važno je:

  • Poštovati konzistentan redosled zauzimanja mutexa.
  • Osigurati da za svako slanje u nebuferisani kanal postoji odgovarajuće čitanje, i obrnuto.
  • Pri radu sa kanalima koristiti select sa default granom ili vremenskim ograničenjima po potrebi.
  • Pažljivo upravljati životnim ciklusom gorutina i zatvaranjem kanala.