Для чего используется `context.WithCancel` в Go? Приведите пример и объясните ключевые моменты.

Ответ

context.WithCancel — это функция из стандартной библиотеки Go, которая создает и возвращает дочерний контекст вместе с функцией для его отмены (CancelFunc).

Основное назначение — управлять жизненным циклом горутин, предоставляя возможность грациозно их остановить.

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

  1. Вы создаете контекст с возможностью отмены: ctx, cancel := context.WithCancel(parentCtx).
  2. Вы передаете ctx в горутины, которые должны быть управляемыми.
  3. Внутри горутин вы используете select для прослушивания канала ctx.Done().
  4. Когда вызывается функция cancel(), канал ctx.Done() закрывается. Это служит сигналом для всех горутин, слушающих этот канал, что им пора завершать работу.

Пример:

func main() {
    // Создаем контекст, который можно отменить
    ctx, cancel := context.WithCancel(context.Background())

    go func(ctx context.Context) {
        fmt.Println("Горутина запущена...")
        select {
        case <-time.After(5 * time.Second): // Имитация долгой работы
            fmt.Println("Операция успешно завершена")
        case <-ctx.Done(): // Ожидание сигнала отмены
            fmt.Println("Операция отменена по сигналу контекста")
            // Здесь может быть логика очистки ресурсов
        }
    }(ctx)

    // Даем горутине поработать 1 секунду
    time.Sleep(1 * time.Second)

    fmt.Println("Отправляем сигнал отмены...")
    cancel() // Отменяем контекст

    time.Sleep(1 * time.Second) // Ждем, чтобы горутина успела завершиться
    fmt.Println("Программа завершена")
}

Ключевые моменты и лучшие практики:

  • Всегда вызывайте cancel(): Чтобы избежать утечек ресурсов, всегда вызывайте функцию cancel, даже если операция завершилась успешно. Лучшая практика — использовать defer cancel() сразу после создания контекста.
  • Пропаганда отмены: Отмена родительского контекста автоматически отменяет все дочерние контексты, созданные на его основе. Это позволяет каскадно завершать целые деревья операций.
  • Не только для отмены: Контекст также используется для передачи данных между вызовами в рамках одного запроса (например, ID запроса или данные аутентификации) с помощью context.WithValue.