Context несёт отмену и дедлайн вниз по вызовам. Values годятся для редких полей запроса вроде trace id, а не для логгера, конфига и внедрения зависимостей.
Конкурентность Go golang собеседование
Context — это сигнал «можно заканчивать» плюс дедлайн, который передают первым аргументом. Почти любой сетевой вызов в современном Go начинается с ctx context.Context: HTTP-сервер кладёт его в Request, клиент, SQL, gRPC его принимают.
Смысл в том, чтобы отмена дошла до самого низа. Горутина перестала ждать. Запрос к БД не жил после ухода клиента. Контекст не кладут в структуру сервиса на всю жизнь процесса: там он быстро становится отменённым или, наоборот, никогда не отменяется.
Аналогия: на совещании передали табличку «сворачиваемся». Каждый следующий докладчик видит ту же табличку. Отменили дочернюю тему — родительское совещание ещё может идти. Отменили родительское — сворачиваются все.
Cancel, timeout, deadline
Дерево: дочерний контекст отменяется вместе с родителем. Обратное неверно: отмена ребёнка не обязана гасить родителя. WithCancel даёт ручную отмену: shutdown, отказ пользователя. WithTimeout и WithDeadline ставят время. Даже при успехе вызывают cancel в defer: иначе таймер живёт до истечения. В цикле запросов это утечка.
func loadUser(parent context.Context, db *sql.DB, id string) error {
ctx, cancel := context.WithTimeout(parent, 3*time.Second)
defer cancel()
var name string
err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", id).Scan(&name)
if err != nil {
return err
}
return nil
}
Timeout на запрос к БД. defer cancel обязателен даже после успешного Scan.
Пример. Хендлер вышел, а фоновая горутина продолжает ходить в БД с Request context. Контекст уже отменён — запросы сыпятся ошибками. Или наоборот: взяли Background и забыли свой timeout — запрос живёт вечно.
Игнорировать ctx в HTTP-клиенте или в Query без Context-варианта — типичный провал. Клиент отвалился, а запрос и горутина живут, держат слот пула БД. Проверка ctx.Err() перед тяжёлой работой экономит CPU. Она не заменяет передачи ctx вниз: цикл без select на Done всё равно не остановится.
Values
WithValue кладёт пару ключ–значение в цепочку. Ключ должен быть неэкспортируемым типом, не строкой: иначе чужой пакет случайно перезапишет то же имя. Уместны сквозные поля запроса: request id, trace span, редко — identity. Логгер, *sql.DB, конфиг, пул — это зависимости конструктора и аргументы, не Values.
Пример. Положили *sql.DB в context, «чтобы не менять сигнатуры». Функция выглядит без зависимостей, а внутри достаёт БД из сумки. Потом в тесте непонятно, какую базу она использует. Лучше аргумент или поле сервиса.
Читать Value — type assertion. Промах типа — нуль и ложный ok. Не кладите туда большие объекты, чтобы не менять сигнатуры: сигнатуру как раз надо менять, когда зависимость настоящая. Для фоновых задач берут context.Background() или отдельный контекст воркера со своим cancel, не Request context после возврата хендлера.
Типичная ошибка
Забыть defer cancel() после WithTimeout. Таймеры копятся. Вторая: хранить Request context в singleton-сервисе или в фоне после ответа. Третья — логгер и БД в контексте вместо аргументов.
На собеседовании
Context первым аргументом. Отмена родителя гасит детей. defer cancel обязателен. Values — редкий ключ запроса с приватным типом. Игнорировать ctx в SQL и HTTP — запрос живёт после клиента.