Go 1.27 находит утечки горутин в работающем сервисе через pprof
Профиль goroutineleak ищет навсегда заблокированные горутины через анализ достижимости, который уже делает сборщик мусора.

Команда Go 2 сентября рассказала в официальном блоге о новом типе профиля goroutineleak, который вошёл в Go 1.27. Он находит горутины, навсегда заблокированные на каналах и примитивах пакета sync, и делает это на работающем продакшен-сервисе, без тестов и без остановки процесса. Профиль доступен через runtime/pprof и через стандартный HTTP-обработчик net/http/pprof по адресу /debug/pprof/goroutineleak.
Для тех, кто держит Go-сервисы в проде, это закрывает старую дыру в инструментах. Обычный goroutine-профиль показывает, сколько горутин сейчас заблокировано, но не отличает штатное ожидание от горутины, которая уже никогда не проснётся. Раньше такие утечки искали руками по стекам или ловили в тестах пакетом goleak; сам релиз Go 1.27 вышел 19 августа, а разбор механизма команда опубликовала только сейчас.
Ключевые выводы
- Профиль
goroutineleakвходит в Go 1.27 и доступен черезruntime/pprofиnet/http/pprof(/debug/pprof/goroutineleak). - Ловит блокировки на отправке и приёме в канал, блокирующем
select,Mutex,RWMutex,WaitGroupиCond. - Не считает утечкой ожидание файлового и сетевого ввода-вывода, системных вызовов и самодельных спинлоков.
- По словам команды Go, метод точный и почти не даёт ложных срабатываний; накладные расходы на память названы пренебрежимо малыми, худший случай для одного цикла GC оценён как O(n²).
- В примере из блога профиль нашёл 116 зависших горутин на одной строке с отправкой в небуферизованный канал.
Что такое утечка горутины и почему её было трудно найти
Утечка горутины по определению из блога Go: горутина заблокирована на операции, условие для продолжения которой больше никогда не наступит. Классический пример: воркеры пишут результаты в небуферизованный канал, а читатель вышел из функции по первой ошибке. Каждый следующий воркер зависает на ch <- result навсегда, и чем дольше живёт процесс, тем больше таких горутин копится, растёт потребление памяти и нагрузка на сборщик мусора.
До Go 1.27 инструментов было три, и ни один не решал задачу для продакшена. goleak проверяет, не остались ли горутины после конкретного теста. Пакет synctest, появившийся в стандартной библиотеке Go 1.25, помогает тестировать конкурентный код с виртуальным временем. Обычный goroutine-профиль показывает заблокированные горутины, но не доказывает, что они не проснутся: всплеск нагрузки выглядит в нём так же, как утечка.
Как профиль отличает утечку от штатной блокировки
Идея, которую описывает автор поста Влад Сайок из команды Go, опирается на работу, которую сборщик мусора и так делает. GC вычисляет достижимость памяти от корней. Команда добавила к этому анализу горутины: горутина считается живой, если она не заблокирована на примитиве конкурентности или если примитив, на котором она ждёт, достижим из какой-нибудь живой горутины. Анализ стартует с незаблокированных горутин и распространяется по доступным им каналам и мьютексам. Всё, что осталось недостижимым после этого обхода, никто уже не разблокирует, значит, это утечка.

Команда Go утверждает, что механизм точный и «почти не даёт ложных срабатываний» (перевод редакции). Есть патологический случай: цепочка горутин, где каждая ждёт примитив, доступный только следующей. Для такой «гирлянды» проверка в худшем случае занимает O(n²) шагов за один цикл GC, поэтому в блоге советуют не включать сбор профиля постоянно, а снимать его периодически, например раз в четыре часа. Память на учёт горутин авторы называют пренебрежимо малой, а GC при этом продолжает работать параллельно с пользовательским кодом.
Разработка выросла из совместного исследования Орхусского университета, Университета Вашингтона в Сент-Луисе и Uber; академическую версию представили на конференции ASPLOS 2025.
Что видно в профиле на живом примере
В блоге разобран сервис, который раз в секунду запускает обработку десяти элементов и на пятом получает ошибку. Функция делает ранний return, оставшиеся воркеры зависают на отправке в канал. Программа подключает net/http/pprof и слушает localhost:6060; профиль снимают обычным способом:
Через несколько минут работы pprof показывает Total: 116 заблокированных горутин, и все они стоят на одной операции: отправке в канал ch. Исправление для примера тривиальное: сделать канал буферизованным на число воркеров, make(chan result, len(ws)), тогда воркеры допишут результаты и завершатся, даже если читатель ушёл.
Что профиль не ловит
- Ожидание файлового и сетевого ввода-вывода и системных вызовов утечкой не считается: горутина, зависшая на чтении из сокета, в профиль не попадёт.
- Самодельные спинлоки и другие пользовательские механизмы блокировки не входят в гарантированный набор.
- Горутина, которая по замыслу ждёт вечно на достижимом канале (например, фоновый слушатель), утечкой не считается, потому что канал достижим из живого кода.
Кому новый профиль ничего не даст: сервисам, где горутины блокируются в основном на сети, а не на каналах, и проектам, которые ещё не перешли на Go 1.27. Профиль работает только в рантайме этой версии; для старых сборок остаются goleak и ручной разбор стеков.
Как включить у себя
- Обновить тулчейн до Go 1.27 (релиз от 19 августа 2026 года, заметки к выпуску) и пересобрать сервис.
- Если
net/http/pprofуже подключён, новый обработчик появится автоматически по пути/debug/pprof/goroutineleak; отдельной настройки не нужно. - Снимать профиль периодически, а не постоянно: команда Go предлагает интервал вроде четырёх часов из-за квадратичного худшего случая.
- Смотреть в первую очередь на места с ранним
returnпри ошибках, таймауты безselectс контекстом и воркер-циклы, у которых читатель может уйти раньше писателей.
Схемы GC и полный пример кода лежат в посте команды Go. Из соседних новостей по инфраструктуре Go-сервисов на сайте есть разбор Kubernetes 1.37 и заметка о tailcat от Tailscale, написанном на Go.
Источники: Go Blog: Goroutine leak profiles, Go 1.27 Release Notes, Go Blog: Go 1.27 is released
Изображение на обложке: Go Authors, логотип Go











