19 вопросов
Контекст переносит дедлайны, отмену и значения по цепочке вызовов. Интерфейс context.Context с методами Deadline(), Done(), Err(), Value(). Используют для отмены горутин, таймаутов и передачи request-scoped данных.
ctx, cancel := context.WithCancel(parent)
defer cancel()context.Background() и context.TODO() - пустые корневые контексты. Производные: WithCancel, WithDeadline, WithTimeout, WithValue. Внутри пакета context реализованы emptyCtx, cancelCtx, timerCtx, valueCtx.
WithTimeout(ctx, d) - то же, что WithDeadline(ctx, time.Now().Add(d)). Дедлайн - абсолютное время; таймаут - относительный интервал. Оба отменяют контекст по истечении времени (или при отмене родителя).
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)WithValue(parent, key, val) создаёт контекст, в котором Value(key) возвращает val. Цепочка ключей непрозрачна для пакета context - только тот слой, который положил ключ, должен его знать. Используют для request ID, трейсов, авторизации. Не класть тяжёлые данные и не злоупотреблять.
ctx = context.WithValue(ctx, key, "value")
v := ctx.Value(key)Оба - пустые контексты без отмены и дедлайна. Background - корневой контекст для main, инициализации, тестов. TODO - когда контекст пока не определён или будет добавлен позже. Семантика одинаковая, различие по смыслу использования.
Читать <-ctx.Done() - канал закрывается при отмене или истечении дедлайна. Либо проверять ctx.Err() != nil. В select: select { case <-ctx.Done(): return ctx.Err(); case result := <-ch: ... }. Функции принимающие контекст должны прерывать работу при ctx.Done().
WithTimeout/WithDeadline создают timerCtx с таймером из time.AfterFunc (или подобным). По срабатыванию таймера вызывается cancel. Контекст отменяется, Done() закрывается. Таймер останавливается при явном вызове cancel или при отмене родителя.
Только данные, привязанные к запросу или цепочке вызова: request ID, trace ID, токены, пользователь. Не передавать опциональные параметры функций и не класть всё подряд - контекст не заменяет аргументы. Ключи - свой тип во избежание коллизий.
Вызывать cancel должен код, создавший контекст - обычно через defer, чтобы освободить ресурсы таймера и сигнализировать отмену. Даже при срабатывании таймаута cancel нужно вызвать. Ранний return без cancel - утечка ресурсов таймера.
ctx, cancel := context.WithTimeout(parent, d)
defer cancel()Done() возвращает канал, который закрывается при отмене контекста (вызов cancel, истечение дедлайна или отмена родителя). Ожидание <-ctx.Done() разблокируется при отмене. Если контекст не отменяем (Background) - Done() возвращает nil; чтение из nil блокируется навсегда.
Дочерний контекст отменяется автоматически при отмене родителя. Родительский cancel() закрывает Done() у всех потомков. Обратное неверно: отмена дочернего не отменяет родителя.
Go 1.21: context.WithCancelCause(parent) - отмена с причиной (cause). context.Cause(ctx) - получить причину отмены. cancel(cause) сохраняет cause; Cause(ctx) возвращает эту причину или ctx.Err(). Удобно для каскадной отмены с причиной.
ctx, cancel := context.WithCancelCause(parent)
cancel(errMyReason)
err := context.Cause(ctx)Таймауты и дедлайны для HTTP-запросов и RPC. Отмена при выходе (graceful shutdown). Передача request-scoped данных (ID, трейс, пользователь). Прерывание длинных операций по сигналу от пользователя или по таймауту.
Соглашение в Go: первый параметр функций - часто ctx context.Context. Удобно для цепочек вызовов и видимости: контекст всегда на одном месте. Рекомендация из официального блога и стайлгайдов. Не жёсткое правило языка.
func Do(ctx context.Context, a int) errorСоздать контекст с отменой. При получении SIGTERM/SIGINT вызвать cancel(). Сервер и воркеры принимают этот контекст и в select проверяют ctx.Done(); при отмене останавливают приём новых задач и завершают работу. Дожидаться выхода через WaitGroup или errgroup.
ctx, cancel := context.WithCancel(context.Background())
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM)
go func() { <-sigCh; cancel() }()
srv.Run(ctx)Технически можно через WithValue, но не рекомендуется. Контекст может копироваться, отменяться и жить дольше одной операции - владение мьютексом или каналом становится неочевидным. Лучше передавать примитивы синхронизации явно аргументами или полями структуры.
Да, если нижележащие вызовы могут блокироваться или делают I/O. Передача контекста позволяет отменить операцию целиком и освободить ресурсы. Функции, принимающие контекст, должны проверять ctx.Done() в циклах и перед долгими операциями.
WithCancelCause возвращает контекст и cancel(cause). При вызове cancel(err) причина сохраняется. context.Cause(ctx) возвращает эту ошибку или nil. Удобно различать отмену по таймауту, по пользователю или по ошибке дочерней операции.
ctx, cancel := context.WithCancelCause(parent)
cancel(fmt.Errorf("reason"))
if err := context.Cause(ctx); err != nil { ... }Да. Один и тот же контекст можно передавать в несколько горутин и использовать одновременно. Методы Context (Done, Err, Value, Deadline) безопасны для конкурентного вызова. Нельзя менять контекст после создания - он неизменяемый (immutable).