ПрограммированиеBackend Go разработчик

Как работает встроенный пакет context в Go? Для чего он используется, как правильно создавать и завершать контексты? Какие ошибки допускают при его применении?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Пакет context — стандарт для управления временем жизни (cancellation, timeout) и передачи метаданных между частями кода, особенно при работе с goroutine и внешними вызовами (HTTP, DB).

Создание:

  • context.Background() — корневой контекст
  • context.WithCancel(parent) — новый с отменой
  • context.WithTimeout(parent, duration) — с таймаутом
  • context.WithValue(parent, key, value) — с данными

Завершать контекст важно для корректного освобождения ресурсов. Пример:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() // далее ctx используется в goroutine/HTTP запросе

Типовой паттерн — передавать context первым аргументом функций, пример:

func process(ctx context.Context) error { ... }

Вопрос с подвохом

Когда можно передавать context.TODO() и context.Background() вместо реального context, и в чём разница между ними?

Многие используют context.Background() как placeholder. Но:

  • context.Background() — корень дерева, используйте только в main() и тестах, при инициализации самого "дерева контекстов".
  • context.TODO() нужен, если пока не ясно, какой должен быть контекст — для временного "затыкания дыр" при рефакторинге. В production коде TODO неприемлем: нужно точно знать, что и куда передаётся.

Примеры реальных ошибок из-за незнания тонкостей темы


История

В микросервисе забыли вызвать cancel() после создания context.WithTimeout() — запросы стопорились, завершённые goroutine оставляли "висящие" таймеры в рантайме, что приводило к утечке памяти.


История

Передача context.Background() вместо пришедшего из запроса context в handler'ах теряла всю цепочку cancellation и trace-id, отчего ограничения таймаутов не работали, отмены запросов не срабатывали.


История

Через context.WithValue() разработчики передавали данные, но ключи были строками. В результате, из-за возможных коллизий ключей из разных пакетов, возникали неожиданные ошибки ("ключ уже занят"). Правильная практика: использовать уникальные типы для ключей.