Назад к вопросам
Junior
110
questionbank

Что такое дедлок при работе с goroutine?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Дедлок (тупик) — это ситуация, когда две или более горутины блокируются, ожидая друг друга, и ни одна из них не может продолжить выполнение. Чаще всего возникает при работе с мьютексами или каналами.

Примеры дедлока:

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

    // Пример дедлока с мьютексами
    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 acquired mu2")
        mu2.Unlock()
        mu1.Unlock()
    }
    
    func goroutine2() {
        mu2.Lock() // Захватывает mu2
        time.Sleep(100 * time.Millisecond) // Ждет немного
        mu1.Lock() // Пытается захватить mu1 (занята goroutine1)
        fmt.Println("goroutine2 acquired mu1")
        mu1.Unlock()
        mu2.Unlock()
    }
    

    Goroutine1 захватила mu1 и ждет mu2. Goroutine2 захватила mu2 и ждет mu1. Обе горутины заблокированы.

  2. Отправка в небуферизованный канал без получателя:

    // Пример дедлока с каналом
    func main() {
        ch := make(chan int)
        //go func() { // Если не запустить горутину для чтения, будет дедлок
        //	<-ch
        //}()
        ch <- 1 // Отправка в канал без активного получателя
        fmt.Println("Sent to channel") // Эта строка не будет достигнута
    }
    

    Отправка в небуферизованный канал блокируется до тех пор, пока другая горутина не начнет читать из него. Если получателя нет, происходит дедлок.

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

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

    Чтение из закрытого непустого канала завершится успешно. Чтение из закрытого и пустого канала или из открытого, но пустого небуферизованного канала без отправителя приведет к блокировке.

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

Для предотвращения дедлоков важно:

  • Соблюдать consistent порядок захвата мьютексов.
  • Убедиться, что для каждой отправки в небуферизованный канал есть соответствующее чтение, и наоборот.
  • При работе с каналами использовать select с default веткой или тайм-аутами при необходимости.
  • Аккуратно управлять временем жизни горутин и закрытием каналов.