Коротко: context.Context передаёт через цепочку вызовов отмену, таймаут и значения запроса — вовремя останавливает ненужную работу и спасает от утечек горутин.
Коротко: context.Context — это способ передать через цепочку вызовов сигнал отмены, дедлайн/таймаут и небольшие значения, привязанные к запросу. Главная задача — вовремя останавливать работу (запросы к БД, HTTP-вызовы, горутины), когда результат уже не нужен. Это спасает от утечек горутин и «висящих» операций. Разберём, как и где его применять.
Зачем вообще нужен context
Представьте HTTP-запрос, который внутри ходит в базу и во внешний сервис. Если клиент отключился или истёк таймаут — продолжать эту работу бессмысленно, она только жжёт ресурсы. context передаёт сигнал «всё, отменяемся» сверху вниз по всем вызовам.
Правило передачи
Контекст принято передавать первым аргументом функции с именем ctx и никогда не хранить в структуре:
func fetchUser(ctx context.Context, id int) (*User, error) {
// ctx прокидывается дальше во все вызовы
return repo.Get(ctx, id)
}
Корневые контексты
В начале цепочки берут «пустой» контекст:
ctx := context.Background() // корневой, в main / на старте
ctx := context.TODO() // заглушка, когда ещё не решили, какой context
В HTTP-обработчике контекст уже есть в запросе: r.Context() — он отменяется автоматически, когда клиент разрывает соединение.
Таймаут и дедлайн
Чаще всего context используют, чтобы ограничить время операции:
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() // ОБЯЗАТЕЛЬНО, иначе утечка таймера
result, err := slowCall(ctx)
if err != nil {
// при таймауте err будет context.DeadlineExceeded
}
defer cancel() нужен всегда — он освобождает ресурсы таймера, даже если операция успела завершиться раньше.
Явная отмена
WithCancel даёт функцию, которой можно отменить контекст вручную — например, остановить пачку горутин:
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
go worker(ctx)
// где-то по условию:
cancel() // обе горутины получат сигнал через ctx.Done()
Как реагировать на отмену
Горутина или цикл должны слушать ctx.Done() и выходить, когда канал закрылся:
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("останавливаюсь:", ctx.Err())
return
default:
doChunkOfWork()
}
}
}
ctx.Err() подскажет причину: context.Canceled (отменили вручную) или context.DeadlineExceeded (истёк таймаут).
Значения в контексте — осторожно
context.WithValue позволяет прокинуть данные запроса (request ID, пользователь), но это не замена параметрам функций:
type ctxKey string
const requestIDKey ctxKey = "requestID"
ctx = context.WithValue(ctx, requestIDKey, "abc-123")
// далее
id, _ := ctx.Value(requestIDKey).(string)
Правило: в context кладут только сквозные «request-scoped» метаданные (трассировка, авторизация), а не бизнес-аргументы. И ключ делают своим типом (не строкой напрямую), чтобы избежать коллизий.
Типичные ошибки
- Забыли
defer cancel()— утечка ресурсов таймера. - Хранят
ctxв поле структуры — антипаттерн; передавайте аргументом. - Не прокидывают
ctxв нижележащие вызовы (БД, HTTP) — тогда отмена не работает. - Кладут в context бизнес-данные вместо параметров — код становится непрозрачным.
- Передают
nilвместо контекста — используйтеcontext.TODO().
Грамотная работа с context — то, что отличает надёжный Go-сервис от того, что течёт горутинами и висит на таймаутах под нагрузкой. Если проектируете сервис с внешними вызовами и хотите заложить отмену и таймауты правильно — это тема для консультации.


Комментарии