Каковы цели и преимущества создания дочерних контекстов в Go (package context)?

Ответ

Создание дочерних контекстов в Go — это ключевой механизм для управления жизненным циклом операций и передачи данных в рамках одного запроса. Основные цели и преимущества:

  1. Пропаганда отмены (Cancellation Propagation)

    • Преимущество: Это главная возможность контекста. Если родительский контекст отменяется (например, пользователь закрыл вкладку браузера, и HTTP-запрос прервался), все дочерние контексты также автоматически отменяются. Это позволяет грациозно остановить всю цепочку операций (например, запросы в базу данных, вызовы других сервисов), связанных с исходным запросом, и освободить ресурсы.
    • Создание: context.WithCancel(parentCtx)
  2. Управление дедлайнами и таймаутами (Deadlines and Timeouts)

    • Преимущество: Можно задать дочернему контексту более строгий (но не более мягкий) дедлайн, чем у родителя. Это полезно, чтобы ограничить время выполнения конкретной подзадачи. Например, на весь HTTP-запрос у нас есть 2 секунды, а на конкретный вызов в базу данных мы хотим выделить не более 500 мс.
    • Создание: context.WithTimeout(parentCtx, duration) или context.WithDeadline(parentCtx, time)
  3. Передача Request-Scoped данных (Passing Values)

    • Преимущество: Позволяет передавать данные, относящиеся ко всему запросу (ID трассировки, информация о пользователе), вниз по стеку вызовов, не загромождая сигнатуры функций. Важно помнить, что этот механизм следует использовать только для данных запроса, а не для передачи опциональных параметров в функции.
    • Создание: context.WithValue(parentCtx, key, value)

Ключевое правило: Отмена или истечение времени жизни дочернего контекста никогда не влияет на его родителя. Это обеспечивает изоляцию и предсказуемость.

Пример использования WithTimeout:

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

    // Запускаем долгую операцию в горутине
    go longOperation(ctx)

    // Ждем сигнала отмены от контекста
    <-ctx.Done()
    fmt.Println("Основная функция: операция отменена по таймауту или завершена.")
    time.Sleep(100 * time.Millisecond) // Даем время горутине вывести сообщение
}

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