← Назад к вопросам

Как узнать, что контекст был отменен?

2.0 Middle🔥 191 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как определить отмену контекста в Go

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

Основной метод: проверка канала Done()

Стандартный идиоматичный способ — использовать канал, возвращаемый методом Done() интерфейса context.Context. Этот канал закрывается при отмене контекста или истечении его дедлайна.

package main

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

func main() {
    // Создаем контекст с отменой
    ctx, cancel := context.WithCancel(context.Background())
    
    go func() {
        // Имитируем работу и отменяем контекст через 50 мс
        time.Sleep(50 * time.Millisecond)
        cancel()
    }()
    
    // Ждем отмены контекста
    select {
    case <-ctx.Done():
        // Канал закрыт - контекст отменен
        fmt.Println("Контекст отменен")
        // Можно получить причину отмены
        fmt.Println("Причина:", ctx.Err()) // "context canceled"
    case <-time.After(100 * time.Millisecond):
        fmt.Println("Таймаут")
    }
}

Метод Err() для получения причины отмены

Метод Err() возвращает nil, если контекст еще активен, или ошибку с причиной отмены:

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            // Проверяем причину отмены
            err := ctx.Err()
            if err != nil {
                switch err {
                case context.Canceled:
                    fmt.Println("Контекст явно отменен")
                case context.DeadlineExceeded:
                    fmt.Println("Достигнут дедлайн")
                default:
                    fmt.Println("Другая причина:", err)
                }
            }
            return
        default:
            // Полезная работа
            time.Sleep(10 * time.Millisecond)
        }
    }
}

Практические паттерны использования

1. В бесконечных циклах

Всегда проверяйте отмену в долго работающих операциях:

func processData(ctx context.Context, dataChan <-chan Data) {
    for {
        select {
        case data := <-dataChan:
            // Обработка данных
        case <-ctx.Done():
            // Аккуратная остановка
            cleanup()
            return
        }
    }
}

2. При выполнении блокирующих операций

Для операций ввода-вывода используйте контекст напрямую, если API поддерживает его:

// HTTP запрос с контекстом
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
    // Ошибка может быть связана с отменой контекста
    if ctx.Err() == context.Canceled {
        fmt.Println("Запрос отменен")
    }
}

3. С дедлайнами и таймаутами

Контексты с дедлайнами автоматически отменяются по истечении времени:

// Контекст с дедлайном
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

// Мониторинг дедлайна
if deadline, ok := ctx.Deadline(); ok {
    if time.Until(deadline) < time.Second {
        fmt.Println("Осталось меньше секунды до дедлайна")
    }
}

Важные особенности

  1. Однократность: После отмены контекст остается отмененным навсегда. Повторные вызовы ctx.Err() будут возвращать одну и ту же ошибку.

  2. Наследование: Дочерние контексты наследуют отмену родительских:

parentCtx, parentCancel := context.WithCancel(context.Background())
childCtx, _ := context.WithTimeout(parentCtx, time.Minute)

// Отмена родителя отменяет дочерний контекст
parentCancel()

select {
case <-childCtx.Done():
    fmt.Println("Дочерний контекст также отменен") // Сработает
case <-time.After(time.Second):
    fmt.Println("Таймаут")
}
  1. Горутины и утечки: Всегда обеспечивайте выход из горутин при отмене контекста, чтобы избежать утечек памяти.

Распространенные ошибки

// ❌ НЕПРАВИЛЬНО: пропуск проверки отмены
func badWorker(ctx context.Context) {
    for {
        // Тяжелая работа без проверки контекста
        // Горутина никогда не завершится при отмене контекста
    }
}

// ✅ ПРАВИЛЬНО: регулярная проверка
func goodWorker(ctx context.Context) {
    ticker := time.NewTicker(100 * time.Millisecond)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            // Периодическая работа
        case <-ctx.Done():
            return // Корректный выход
        }
    }
}

Заключение

Отслеживание отмены контекста — фундаментальный аспект написания корректных конкурентных программ на Go. Используйте <-ctx.Done() для ожидания отмены и ctx.Err() для определения причины. Всегда проектируйте долго работающие операции и горутины с учетом возможности graceful shutdown через механизм контекстов. Это обеспечивает не только корректное завершение программ, но и эффективное управление ресурсами и отзывчивость приложений.

Как узнать, что контекст был отменен? | PrepBro