Как прервать выполнение goroutine без остановки главной программы в Go
Что проверяют
Знаете ли вы главное: снаружи горутину убить нельзя, аналога Thread.stop в Go нет. Остановка всегда кооперативная, горутина сама проверяет сигнал и выходит. Следом спрашивают, как дождаться завершения и не допустить утечки.
Как отвечать
Сигнал отмены передают через context.Context, это стандарт для библиотек и серверного кода:
func worker(ctx context.Context, jobs <-chan Job) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case j := <-jobs:
process(ctx, j)
}
}
}Что нужно проговорить:
- создание:
context.WithCancel,WithTimeout,WithDeadline.cancelвызывают всегда, обычно черезdefer, иначе контекст висит до отмены родителя, иgo vetна это ругается.WithCancelCauseпозволяет передать причину; - блокирующие вызовы:
selectспасает только там, где горутина ждёт на канале. Сеть и база прерываются, если контекст передан внутрь:http.NewRequestWithContext,db.QueryContext. Долгий вычислительный цикл сам периодически проверяетctx.Err(); - канал
done: вариант без контекста. Сигнал подают черезclose(done), а не отправкой значения: закрытие видят все горутины сразу, и отправитель не зависнет, если получатель уже вышел; - ожидание:
sync.WaitGroupилиerrgroup.Group, который ещё и отменяет общий контекст при первой ошибке. В Go 1.25 появилсяwg.Go(f), он сам делаетAddиDone.
Главная программа при этом не страдает: отмена затрагивает только тех, кто слушает этот контекст.
Где спотыкаются
Ждут завершения через time.Sleep после отмены. Это гадание: горутина может не успеть, а если успеет, программа просто теряет время. Нужен WaitGroup или канал с результатом.
Пишут select с веткой default и Sleep внутри. Получается опрос с задержкой реакции до целого интервала, а блокирующий select на ctx.Done() и канале работы реагирует сразу.
Думают, что panic в горутине останется её личным делом. Неперехваченная паника в любой горутине роняет весь процесс, поэтому recover ставят в самой горутине, а не в main.