Глиф Учебники

all goroutines are asleep — deadlock

Справочник · Go · 2 минуты чтения

Текст
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()

Go остановил программу, потому что все её горутины чего-то ждут — значения из канала, места в канале, Wait, мьютекса (замка для общих данных), — и ни одна не может продолжить. В квадратных скобках — чего ждёт горутина: chan send — отправки в канал, chan receive — получения из канала. Ниже указаны файл и строка: откройте эту строку кода и найдите свой случай среди разделов ниже.

Отправка в канал без получателя

Go
ch := make(chan int)
ch <- 1 // ждёт получателя вечно
fmt.Println(<-ch)

Канал без буфера передаёт значение только из рук в руки: отправка ждёт, пока кто-то в другой горутине начнёт получать. Здесь получатель — та же горутина, строкой ниже, и до него дело не дойдёт. Решения: отправлять из отдельной горутины или дать каналу буфер.

Go
package main

import "fmt"

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
    }()
    fmt.Println(<-ch)

    buf := make(chan int, 1)
    buf <- 2
    fmt.Println(<-buf)
}
Вывод
1
2

range по каналу, который не закрыли

Go
for v := range ch {
    fmt.Println(v)
}

Цикл заканчивается, только когда канал закрыт. Если отправитель отправил все значения, но не вызвал close(ch), range ждёт следующего значения вечно — это chan receive в сообщении. Закрывайте канал там, где отправка закончилась: close(ch) после цикла отправки или defer close(ch) в начале горутины-отправителя.

WaitGroup: Add и Done не сошлись

wg.Wait() ждёт, пока число Done не сравняется с суммой Add. Если Add(1) вызвано больше раз, чем запущено горутин, или горутина выходит, не вызвав Done, Wait не дождётся. Правила:

WaitGroup передана по значению

Go
func worker(wg sync.WaitGroup) { // ошибка: копия
    defer wg.Done()
}

Функция получила копию WaitGroup и уменьшает счётчик у копии, а main ждёт оригинал. Передавайте указатель: func worker(wg *sync.WaitGroup) и вызов worker(&wg). То же относится к sync.Mutex. Такую ошибку находит go vet: сообщение passes lock by value.

Горутина ждёт сама себя

Повторный mu.Lock() в той же горутине без Unlock между ними блокирует её навсегда: мьютекс в Go не запоминает, кто его взял. Если других работающих горутин нет — та же ошибка deadlock.

Когда Go не скажет «deadlock»

Сообщение появляется, только когда заблокированы все горутины. Если хоть одна ещё работает — например, HTTP-сервер ждёт подключений, — зависшие горутины просто молча висят. Тогда помогает снимок всех горутин: в Linux и macOS отправьте программе сигнал SIGQUIT (Ctrl+\ в терминале), и она напечатает стек каждой горутины перед выходом.

Подробно о каналах и WaitGroup — в уроке Горутины и каналы.