Как отследить и исправить deadlock в Go при работе с каналами и горутинами
Что проверяют
Знаете ли вы, какие блокировки рантайм ловит сам, а какие нет, и умеете ли снять снимок зависших горутин на живом процессе. Следом обычно дают кусок кода с каналами и просят найти, где он встанет.
Как отвечать
Сначала разделите два случая. Если заблокированы все горутины, рантайм падает с fatal error: all goroutines are asleep - deadlock!. Но в сервисе всегда есть горутины, ждущие сеть или таймеры, поэтому частичный deadlock рантайм не видит: несколько горутин висят навсегда, память растёт, запросы не отвечают. По сути это утечка горутин.
Как найти:
- дамп стеков:
SIGQUIT(kill -QUIT <pid>илиCtrl+\) печатает стеки всех горутин с состоянием вродеchan send,chan receive,selectи временем ожидания; - pprof:
import _ "net/http/pprof"и запрос/debug/pprof/goroutine?debug=2. Сотни горутин на одной строке сchan sendпочти всегда указывают на причину; - тесты:
go test -timeoutпри зависании печатает стеки,go.uber.org/goleakпроверяет, что после теста не осталось горутин, аtesting/synctest(стабилен с Go 1.25) сообщает о горутинах, навсегда заблокированных внутри теста.
Типичные причины и исправления:
- отправка без получателя: небуферизованный канал, читатель вышел раньше. Лечится
selectсctx.Done()у отправителя; for range chбезclose: канал закрывает тот, кто в него пишет, и только он;sync.WaitGroup:Addвнутри горутины или забытыйDone.Addвызывают доgo,Doneчерезdefer;- мьютексы: повторный
Lockв той же горутине (мьютекс не реентерабелен) или разный порядок захвата двух мьютексов.
Для групп горутин удобен errgroup.WithContext: первая ошибка отменяет контекст, остальные выходят.
Где спотыкаются
Надеются на -race. Детектор гонок ищет одновременный доступ к памяти без синхронизации, deadlock он не показывает.
Лечат зависание буфером make(chan T, 100). Проблема откладывается до заполнения буфера и возвращается под нагрузкой.
«Проверяют» deadlock через select с default или time.Sleep. Это маскирует ошибку, а не находит её: неотправленное значение и зависшая горутина никуда не делись.