Deadlock - это остановка из-за циклического ожидания. Утечка горутин - работа, которая продолжается, хотя результат больше никому не нужен. Они часто идут вместе и видны в профиле горутин.

Типовые причины

  • два мьютекса захватываются в разном порядке;
  • небуферизованный канал пишет без читателя;
  • WaitGroup ждёт задачу, которая не вызывает Done;
  • получатель вышел по ошибке, а отправители остались на записи;
  • внешний вызов без дедлайна не возвращается;
  • рекурсивный вызов берёт тот же мьютекс повторно.

Пример взаимной блокировки:

first.Lock()
second.Lock()

Если одна функция меняет порядок на second.Lock, first.Lock, встречные вызовы встанут. Единый порядок по ID или сортировке ресурсов убирает цикл.

Профиль горутин

Сервис завис. Включаем pprof на служебном порту и смотрим:

go tool pprof -top http://localhost:6060/debug/pprof/goroutine

Стеки группируются по месту ожидания. Детальный вывод debug=2 показывает chan send, chan receive, sync.Mutex.Lock и время ожидания. Если тысячи стеков совпадают, обычно виновник уже найден.

trace помогает увидеть, кто кого ждёт и почему планировщик не продвигается.

goleak

Тест на завершение:

func TestWorkerStops(t *testing.T) {
    defer goleak.VerifyNone(t)

    ctx, cancel := context.WithCancel(context.Background())
    go worker(ctx)
    cancel()
}

VerifyNone проверяет, что новых горутин не осталось. Для библиотечного кода это дешёвая проверка на утечку. Для сервера используют VerifyTestMain, потому что фоновые горутины должны завершаться после всех тестов.

Race detector

go test -race ./... находит гонку, но не deadlock и не логическую атомарность. Поэтому дополнительно нужны:

  • тест с ограничением времени на завершение;
  • параллельные встречные вызовы;
  • отмена на каждой стадии;
  • проверка, что все каналы закрыты и отправители завершились.

Время в тесте выбирают так, чтобы ошибка ожидания была заметна, а не просто медленная.

Коротко

  • Профиль горутин показывает, где именно стоят заблокированные работы.
  • Единый порядок захвата ресурсов предотвращает взаимные блокировки.
  • goleak проверяет завершение, -race проверяет память, тест по времени - остановку.
  • Отмена и закрытие каналов проверяются как отдельные сценарии.