Как с помощью контекста завершить работу горутины в Go
Что проверяют
Понимаете ли вы, что контекст не убивает горутину, а только сигналит ей, и выйти она должна сама. Следом спрашивают, как дождаться реального завершения и чем WithCancel отличается от WithTimeout.
Как отвечать
Базовая схема:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
for {
select {
case <-ctx.Done():
return
case job := <-jobs:
process(ctx, job)
}
}
}()- сигнал:
cancel()закрывает каналctx.Done(), и все, кто его слушает, получают сигнал одновременно. Причину покажетctx.Err():context.Canceledилиcontext.DeadlineExceeded; - точки проверки: горутина слушает
ctx.Done()в каждомselect, где может заблокироваться. Долгие операции получаютctxпараметром, иhttp.NewRequestWithContextилиdb.QueryContextпрервутся сами; - ожидание: отмена не значит, что горутина уже вышла. Дожидаются через
sync.WaitGroupилиerrgroup.WithContext, который ещё и отменяет остальных при первой ошибке.
Дальше варианты: WithTimeout и WithDeadline отменяют по времени, WithCancelCause (Go 1.20) передаёт причину, которую читает context.Cause, а signal.NotifyContext связывает контекст с SIGINT и SIGTERM для graceful shutdown. cancel вызывают всегда, даже для таймаута: иначе контекст висит до срабатывания таймера, и go vet об этом предупреждает.
Где спотыкаются
Ставят default в select без блокирующей работы. Цикл превращается в активное ожидание и съедает ядро.
Используют time.Sleep внутри горутины: пока она спит, отмену не видит. Нужен select по ctx.Done() и time.After или тикеру.
Отправляют в канал без select. Если читатель уже ушёл по отмене, горутина висит навсегда, это классическая утечка горутин.