История вопроса:
Goroutine leak — это ситуация, когда goroutine продолжает существовать и висит в памяти, хотя фактически "лишилась смысла" (вычисления завершились, данные не нужны, но нет условия выхода). Подобно утечкам памяти, но для потоков исполнения. Это критично для Go — большая нагрузка может привести к исчерпанию ресурсов.
Проблема:
В отличие от других языков, где прямое управление потоками часто ведёт к их ручному закрытию, в Go горутины запускаются легко, но не всегда корректно завершаются. Частая ошибка: основная логика завершилась, а горутина "замерзла" — ждёт данных по закрытому каналу или вообще не дождётся сигнала.
Решение:
Для предотвращения утечек используют контролирующие конструкции: context, закрытие каналов, сигнальные переменные. Важно заранее проектировать пути выхода из каждой горутины, использовать defer для очистки. Пример:
func worker(ctx context.Context, jobs <-chan int, results chan<- int) { for { select { case <-ctx.Done(): return case job, ok := <-jobs: if !ok { return } results <- job * 2 } } }
Ключевые особенности:
Можно ли просто закрыть канал, чтобы остановить Goroutine?
Не всегда. Если в select есть другие case или нет проверки закрытия через ok, горутина может остаться "висеть".
val, ok := <-ch if !ok { return } // Так корректнее
Что произойдёт, если забыть обработать context.Done в select?
Горутина никогда не узнает, что отмена произошла — она останется "вечной". Это прямой путь к leak.
Можно ли поймать leak с помощью go runtime?
Нет стандартного инструмента отслеживания leak. Необходимо мониторить количество активных goroutine через runtime.NumGoroutine или использовать leak detector сторонних библиотек.
В системе рассылки пушей запускают goroutine на каждое входящее сообщение, но забывают останавливать их при отмене контекста или закрытии канала — сотни "мертвых" горутин висят в памяти.
Плюсы:
Минусы:
Работа goroutine контролируется через контекст, на уровне бизнес-логики проверяется выход из select, после успешной отправки/обработки канал завершается.
Плюсы:
Минусы: