Как работает дедлок и как его избежать
Что проверяют
Понимаете ли вы, откуда берётся взаимная блокировка и как она выглядит именно в Go: на мьютексах, каналах и sync.WaitGroup. Следом обычно просят найти дедлок в куске кода или спрашивают, почему рантайм поймал одну блокировку, а другую нет.
Как отвечать
Дедлок это состояние, когда горутины ждут друг друга по кругу и ни одна не может продолжить. Для него нужны четыре условия Коффмана одновременно: взаимное исключение, удержание ресурса во время ожидания другого, отсутствие принудительного отъёма и циклическое ожидание. Сломали любое из них, и дедлока не будет.
Типичные источники в Go:
- порядок захвата: одна горутина берёт
mu1, потомmu2, другая наоборот; - повторный
Lock:sync.Mutexне реентерабельный, повторный захват в той же горутине висит навсегда; - небуферизованный канал: отправка без готового получателя блокирует горутину, в том числе
main; WaitGroup:Waitпри забытомDoneилиAdd, вызванный уже внутри запущенной горутины;rangeпо каналу, который никто не закрывает.
Как избегать: единый порядок захвата блокировок, короткие критические секции без внешних вызовов под мьютексом, defer mu.Unlock() сразу после Lock, закрытие канала на стороне отправителя. Ожидание с ограничением делается через select с ctx.Done() или time.After. У sync.Mutex тайм-аута нет, есть только TryLock (с Go 1.18), и нужен он редко.
Про диагностику: рантайм завершает программу с fatal error: all goroutines are asleep только если заснули все горутины. Частичный дедлок в сервере он не видит, его ищут по дампу стеков: SIGQUIT или /debug/pprof/goroutine?debug=2. В тестах зависшие горутины ловит goleak.
Где спотыкаются
Считают, что Go сам найдёт любой дедлок. Пока жива хотя бы одна горутина, например HTTP-сервер, зависшие горутины просто копятся и съедают память.
Путают дедлок с гонкой данных и предлагают флаг -race. Детектор гонок ищет несинхронизированный доступ к памяти, взаимные блокировки он не обнаруживает.
Обещают «мьютекс с тайм-аутом». В стандартной библиотеке такого нет, и честный ответ сводится к select по каналу с контекстом или к пересмотру порядка захвата.