Sobes.tech
Junior

Какво е deadlock при работа с goroutine?

sobes.tech AI

Отговор от AI

Deadlock (задръстване) е ситуация, когато две или повече горутини блокират една друга, очаквайки една друга, и никоя от тях не може да продължи изпълнението. Най-често се случва при работа с мютекси или канали.

Примери за deadlock:

  1. Блокиране на мютекси в различен ред:

    // Пример за deadlock с мютекси
    var mu1 sync.Mutex
    var mu2 sync.Mutex
    
    func goroutine1() {
        mu1.Lock() // Взема mu1
        time.Sleep(100 * time.Millisecond) // Чака малко
        mu2.Lock() // Опитва се да вземе mu2 (който държи goroutine2)
        fmt.Println("goroutine1 взема mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Взема mu2
        time.Sleep(100 * time.Millisecond) // Чака малко
        mu1.Lock() // Опитва се да вземе mu1 (който държи goroutine1)
        fmt.Println("goroutine2 взема mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 взема mu1 и чака mu2. Goroutine2 взема mu2 и чака mu1. И двете горутини са блокирани.

  2. Изпращане към небуфериран канал без получател:

    // Пример за deadlock с канал
    func main() {
        ch := make(chan int)
        //go func() { // Ако не стартираме горутина за четене, ще има deadlock
        //	<-ch
        //}()
        ch <- 1 // Изпращане към канала без активен получател
        fmt.Println("Изпратено към канала") // Тази линия няма да бъде достигната
    }
    

    Изпращането към небуфериран канал блокира, докато друга горутина не започне да чете от него. Ако няма получател, възниква deadlock.

  3. Четене от канал без изпращач (след затваряне или празен буфериран):

    // Пример за deadlock при четене от канал
    func main() {
        ch := make(chan int, 1) // Буфериран канал с 1 елемент
        ch <- 1 // Изпратихме 1
        close(ch) // Затворихме канала
        <-ch // Прочетохме 1
        <-ch // Опитваме се да прочетем отново от затворен и празен канал - deadlock
    }
    

    Четенето от затворен, непълен канал ще завърши успешно. Четенето от затворен и празен или от отворен, но празен небуфериран канал без изпращач ще доведе до блокиране.

Golang runtime може да открие определени видове deadlock (например, когато всички блокирани горутини очакват получаване или изпращане на единствена горутина), и в такъв случай програмата ще се прекъсне с съобщение "fatal error: all goroutines are asleep - deadlock!". Въпреки това, не всички deadlock-и могат да бъдат автоматично открити.

За предотвратяване на deadlock-и е важно:

  • Да се спазва последователен ред при взимане на мютекси.
  • Да се гарантира, че за всяко изпращане към небуфериран канал има съответно четене, и обратното.
  • При работа с канали да се използва select с default клон или тайм-аутове при необходимост.
  • Внимателно да се управлява жизненият цикъл на горутините и затварянето на каналите.