Какими способами можно отменить операцию в Go
Что проверяют
Понимаете ли вы, что горутину нельзя убить снаружи и отмена в Go всегда кооперативная: операция сама проверяет сигнал и выходит. Следом спрашивают, как отменить HTTP-запрос или запрос к базе и чем WithTimeout отличается от WithDeadline.
Как отвечать
context.Context: стандартный способ.WithCancelвозвращает контекст иcancel,WithTimeoutиWithDeadlineотменяют сами по времени. Горутина слушает<-ctx.Done()вselect, причину даётctx.Err():context.Canceledилиcontext.DeadlineExceeded.cancelвызывают всегда, обычно черезdefer, иначе ресурсы висят до отмены родителя, иgo vetна это ругается;- передача вниз:
http.NewRequestWithContext,db.QueryContext, клиенты gRPC и Redis принимаютctxпервым аргументом и прерывают операцию сами. Отмена родителя каскадом отменяет потомков; - причина отмены: с Go 1.20 есть
WithCancelCauseиcontext.Cause, с 1.21AfterFuncиWithoutCancelдля фоновой работы, которая должна пережить запрос; - закрытие канала:
done := make(chan struct{})иclose(done). Закрытие видят все читатели сразу, это широковещательный сигнал. Приём старше пакетаcontextи уместен внутри одного компонента; errgroup.WithContext: изgolang.org/x/sync, первая ошибка в любой горутине отменяет контекст остальных;signal.NotifyContext: отмена поSIGINT/SIGTERMдля graceful shutdown.
Подчеркните, что отмена лишь сигнал. Цикл без проверки ctx.Done() досчитает до конца, поэтому в долгой CPU-работе проверку ставят в итерацию или раз в N шагов.
Где спотыкаются
Предлагают булев флаг, который горутина читает в цикле. Без синхронизации это гонка данных, её поймает go test -race. Если уж флаг, то atomic.Bool, но контекст всё равно лучше.
Хранят контекст в поле структуры или передают nil. Контекст идёт первым параметром функции, а без родителя берут context.Background() или context.TODO().
Забывают, что после отмены результат может быть некуда отдать: запись в небуферизованный канал, который уже никто не читает, блокирует горутину навсегда, и получается утечка.