context.Context переносит через цепочку вызовов три вещи: сигнал отмены, дедлайн и значения уровня запроса. Почти любая функция, которая ходит в сеть или базу, принимает ctx первым аргументом.
Интерфейс
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{} // закрывается при отмене
Err() error // Canceled или DeadlineExceeded после отмены
Value(key any) any
}
Дерево контекстов
Корень: context.Background() (или context.TODO(), когда ещё не решили). От него строятся потомки:
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel() // обязательно, иначе ресурсы таймера живут до дедлайна
WithCancel: отмена вручную.WithTimeout,WithDeadline: отмена по времени. Итоговый дедлайн не позже родительского.WithValue: добавляет значение.- Go 1.20
WithCancelCauseиcontext.Cause: отмена с причиной. - Go 1.21
WithoutCancel(потомок не отменяется вместе с родителем, например для фоновой записи аудита после ответа),AfterFunc(вызвать функцию после отмены),WithDeadlineCause,WithTimeoutCause.
Отмена идёт сверху вниз: отменили родителя, отменены все потомки. Отмена потомка не трогает родителя.
Как слушать отмену
func worker(ctx context.Context, jobs <-chan Job) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case j, ok := <-jobs:
if !ok { return nil }
process(j)
}
}
}
Контекст ничего не прерывает принудительно. Он только закрывает канал Done, а код должен сам его проверять. Библиотеки (net/http, database/sql, драйверы) делают это за вас, если передать им ctx.
В HTTP
r.Context() отменяется, когда клиент разорвал соединение или сервер завершает обработку. Передавайте его в запросы к базе: если пользователь ушёл, запрос к базе прервётся. Исходящий запрос: http.NewRequestWithContext(ctx, ...).
Значения
type ctxKey struct{}
ctx = context.WithValue(ctx, ctxKey{}, requestID)
id, _ := ctx.Value(ctxKey{}).(string)
- Ключ своего неэкспортируемого типа, чтобы не было коллизий между пакетами (не строка).
- Только данные уровня запроса: request id, данные авторизации, трассировка. Не передавайте через контекст обязательные параметры функции, логгер по умолчанию или соединение с базой.
- Поиск значения идёт вверх по цепочке, это O(глубины).
Правила
ctxпервый аргумент, имяctx, не храните его в полях структуры (исключения редки).- Не передавайте nil, используйте
context.TODO(). - Всегда вызывайте
cancel, даже если операция завершилась успешно (go vetпредупреждает о потерянной cancel). - Контекст безопасен для одновременного использования из многих горутин.
Частые ошибки
- Забыть
defer cancel(). - Запустить фоновую работу с
r.Context()и удивляться отмене после ответа: нуженWithoutCancelили свой контекст. - Не проверять
ctx.Done()в долгом цикле. - Класть в контекст всё подряд.
Что спросят на собесе
- Зачем нужен context и что в нём лежит.
- Как распространяется отмена в дереве.
- Почему обязательно вызывать cancel.
- Что можно и нельзя класть в WithValue, какого типа делать ключ.
- Что появилось в 1.20-1.21: Cause, WithoutCancel, AfterFunc.