Что такое `context.Context` в Go и для чего он используется?

Ответ

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

Основные сценарии использования:

  1. Отмена (Cancellation): Если пользователь отменил запрос (например, закрыл вкладку браузера), context позволяет каскадно передать сигнал отмены всем горутинам, которые обрабатывали этот запрос (например, запросы к БД, другим сервисам), чтобы они прекратили работу и освободили ресурсы.

    • Создается с помощью context.WithCancel().
    • Проверяется через канал <-ctx.Done().
  2. Таймауты и Дедлайны (Timeouts and Deadlines): Можно установить максимальное время на выполнение операции. Если время истекло, контекст будет отменен. Это критически важно для предотвращения "зависания" горутин в ожидании ответа от медленного внешнего сервиса.

    • Создается с помощью context.WithTimeout() или context.WithDeadline().
  3. Передача данных (Value Propagation): Позволяет передавать данные вниз по стеку вызовов, например, ID запроса, информацию для трассировки или данные о пользователе.

    • Создается с помощью context.WithValue().
    • Важно: Считается плохой практикой использовать WithValue для передачи обязательных параметров функции. Параметры должны передаваться явно. Контекст предназначен только для сквозных, request-scoped данных.

Пример с таймаутом:

package main

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

func longOperation(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
        fmt.Println("Операция успешно завершена")
    case <-ctx.Done(): // Этот канал закроется при отмене контекста
        fmt.Println("Операция прервана по таймауту:", ctx.Err())
    }
}

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

    longOperation(ctx) // Операция будет прервана через 2 секунды
}

Правила использования Context:

  • Передавайте Context как первый аргумент функции, с именем ctx.
  • Никогда не храните Context внутри структуры. Передавайте его явно в каждый вызов.
  • Цепочка вызовов должна пробрасывать контекст дальше.
  • Никогда не передавайте nil Context. Если не уверены, какой использовать, используйте context.Background().