context.Context

Пакет context в Go используется для управления временем выполнения и отмены операций, особенно при работе с горутинами, сетевыми запросами и базами данных. Он позволяет передавать сигналы отмены, дедлайны и другие значения через API без необходимости изменения сигнатур всех функций.

Основные концепции:

  1. Контекст (Context) – объект, который передаётся в функции и горутины, обеспечивая управление временем выполнения.
  2. Отмена (Cancelation) – позволяет принудительно завершить операции.
  3. Дедлайн (Deadline) – устанавливает максимальное время выполнения.
  4. Значения (Values) – можно передавать дополнительные данные в контексте.
type Context interface {

    // Deadline возвращает время когда этот Context будет отменен.
    Deadline() (deadline time.Time, ok bool)

    // Done возвращает канал, который закрывается когда Context отменяется
    Done() <-chan struct{}

    // Err объясняет почему контекст был отменен, после того как закрылся канал Done.
    Err() error

    // Value возвращает значение ассоциированное с ключем или nil.
    Value(key interface{}) interface{}
}

Код почти дословно иллюстрирует, для чего используется контекст:

  • чтобы устанавливать дедлайн исполнения блока кода;
  • оповещать об окончании исполнения блока кода;
  • узнавать причину отмены контекста;
  • получать значения по ключу.

Основные функции:

  • context.Background() – создаёт базовый контекст, используется как корневой, следует использовать только на самом высоком уровне, как корень всех производных контекстов.
  • context.TODO() – заглушка, если ещё не ясно, какой контекст использовать.
  • context.WithCancel(parent) – создаёт новый контекст с возможностью отмены.
  • context.WithTimeout(parent, duration) – создаёт контекст с тайм-аутом.
  • context.WithDeadline(parent, time) – задаёт конкретное время дедлайна.
  • context.WithValue(parent, key, value) – создаёт контекст с дополнительными данными. Следует использовать как можно реже, и его нельзя применять для передачи необязательных параметров. Это делает API непонятным и может привести к ошибкам. Такие значения должны передаваться как аргументы.

Все методы безопасны для одновременного использования в разных go-рутинах.

Интерфейса достаточно для использования в любых местах, где код может «зависнуть». Это любое сетевое взаимодействие, а также долгие задачи, не выходящие за рамки процесса ОС. Кроме того, контекст можно использовать для неявной передачи параметров в функции. Хорошей практикой, считается "управлять" горутинами через контекст

Best practices

Передавайте context.Context первым аргументом в функции

Такой порядок принят в стандартной библиотеке Go.
Правильно:

func fetchData(ctx context.Context, url string) error {

Неправильно:

func fetchData(url string, ctx context.Context) error {

Не сохраняйте context.Context в структуре

Контексты живут ограниченное время, поэтому их нельзя хранить в полях структуры.
Правильно:

func (s *Server) HandleRequest(ctx context.Context, req Request) {

Неправильно:

type Server struct {
    ctx context.Context
}

Не передавайте nil в качестве контекста

Если контекст неизвестен, используйте context.TODO().
Правильно:

ctx := context.TODO()

Неправильно:

ctx := nil // вызовет панику

Используйте defer cancel() для WithCancel(), WithTimeout() и WithDeadline()

Это гарантирует, что ресурсы контекста будут освобождены.
Правильно:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

Неправильно:

ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) // cancel() никогда не вызывается

Не передавайте контекст в struct{} через WithValue()

Контекст предназначен для передачи метаданных, а не для конфигурации.
Правильно:

ctx := context.WithValue(ctx, "userID", 123)

Неправильно:

ctx := context.WithValue(ctx, "config", myStruct) // так делать не стоит

Не используйте context.Background() в обработчиках запросов

Вместо него используйте контекст от сервера или родительский контекст.
Правильно:

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
}

Неправильно:

func handler(w http.ResponseWriter, r *http.Request) {
    ctx := context.Background() // теряется связь с клиентом
}

Используйте select для обработки ctx.Done() в горутинах

Чтобы корректно завершать горутину, когда контекст отменяется.
Правильно:

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Остановка по контексту")
            return
        default:
            fmt.Println("Работаю...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

Неправильно:

func worker(ctx context.Context) {
    for {
        fmt.Println("Работаю...")
        time.Sleep(500 * time.Millisecond)
    }
}

Не используйте context.WithValue() для передачи больших объектов

Контекст предназначен для передачи лёгких метаданных (например, userID), а не больших структур.
Правильно:

ctx := context.WithValue(ctx, "userID", 123)

Неправильно:

ctx := context.WithValue(ctx, "user", largeUserStruct) // может привести к утечкам памяти

Дополнительно: