Встречается на 33% собеседований по Go

Как устроены контексты в Go

Контексты в Go (context.Context) — это интерфейс для передачи метаданных, управления сроками выполнения и отменой операций в иерархии вызовов функций. Основная цель — дать возможность остановить выполнение программы (например, запросов или подпроцессов) по требованию, что критично в сетевых приложениях, где операции могут занимать слишком много времени или становиться ненужными.

Как это работает

Контекст — это интерфейс с четырьмя методами: Deadline(), Done(), Err() и Value(). Ключевые особенности:

  • Пропагация по горутинам: контекст безопасно передается между горутинами, позволяя управлять операциями, выполняемыми параллельно.
  • Отмена: контекст можно отменить, что автоматически сигнализирует всем получателям через закрытие канала Done(). Получатели должны проверять Done() и завершать работу.
  • Сроки выполнения: можно установить таймаут или дедлайн, после которого контекст отменяется автоматически.
  • Значения: контекст может хранить пары ключ-значение для передачи данных, специфичных для запроса (например, ID сессии или токены авторизации).

Создание контекстов

Пакет context предоставляет несколько функций для создания контекстов:

  • context.Background() — пустой контекст, который никогда не отменяется. Используется в main() и при инициализации.
  • context.TODO() — заглушка для мест, где контекст должен быть, но еще не передан. Применяется при разработке и рефакторинге.
  • context.WithCancel(parent) — создает дочерний контекст с функцией отмены cancel.
  • context.WithDeadline(parent, deadline) — создает контекст с конкретным временем отмены.
  • context.WithTimeout(parent, timeout) — создает контекст, который отменится через заданный промежуток времени.

Все производные контексты создаются от родительского и наследуют его свойства. При отмене родительского контекста все дочерние также отменяются.

Пример

go
package main

import (
    "context"
    "fmt"
    "time"
)

func operation1(ctx context.Context) {
    select {
    case <-time.After(5 * time.Second):
        fmt.Println("operation1 completed")
    case <-ctx.Done():
        fmt.Println("operation1 cancelled")
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel() // важно вызывать cancel, чтобы освободить ресурсы

    go operation1(ctx)

    // Дожидаемся завершения или отмены операции
    <-ctx.Done()
    if err := ctx.Err(); err != nil {
        fmt.Println("main:", err)
    }
}

В этом примере operation1 прерывается, если контекст отменяется до истечения 5 секунд. В main мы ждем отмены контекста и выводим причину (context.DeadlineExceeded).

Подводные камни

  • Всегда вызывайте cancel: если не вызвать функцию отмены, ресурсы (таймеры, горутины) будут утекать. Используйте defer cancel() сразу после создания.
  • Не храните контекст в структурах: контекст должен передаваться явно как первый аргумент функции. Хранение в структурах затрудняет тестирование и нарушает идиоматичность.
  • Не передавайте nil контекст: вместо этого используйте context.TODO() или context.Background().
  • Значения контекста не должны быть критичными: они предназначены для запрос-специфичных данных, а не для передачи параметров функций.
  • Проверяйте ctx.Err(): после отмены контекста Err() возвращает context.Canceled или context.DeadlineExceeded, что помогает понять причину.

Когда использовать

Контексты обязательны в HTTP-обработчиках, при работе с базами данных, внешними API и любыми длительными операциями. Они позволяют корректно завершать работу при отмене запроса или превышении таймаута, экономя ресурсы и улучшая отзывчивость приложения.

Коротко

  • Контекст — это интерфейс для отмены, таймаутов и передачи метаданных.
  • Создавайте контексты через context.Background(), WithCancel, WithTimeout, WithDeadline.
  • Всегда вызывайте cancel через defer, чтобы избежать утечек.
  • Проверяйте ctx.Done() в длительных операциях и завершайте работу при отмене.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы