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

Для чего контекст в Go?

1.0 Junior🔥 281 комментариев
#Основы Go

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

Для чего контекст (Context) в Go

Context (контекст) — это один из фундаментальных паттернов в Go, позволяющий управлять отмены операций, временем ожидания и передавать значения через цепочку вызовов goroutines. Это критически важно для написания надёжного асинхронного кода.

Основные цели контекста

1. Управление отменой операций Контекст позволяет сигнализировать goroutines о необходимости прекратить работу.

import "context"
import "time"

func main() {
    // Создаём контекст с отменой
    ctx, cancel := context.WithCancel(context.Background())
    
    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Goroutine отменена")
                return
            default:
                fmt.Println("Работаю...")
                time.Sleep(1 * time.Second)
            }
        }
    }()
    
    time.Sleep(3 * time.Second)
    cancel()  // Отменяем контекст
    time.Sleep(1 * time.Second)
}

2. Управление временем ожидания (timeout) Контекст может автоматически отменяться через определённое время.

func fetchDataWithTimeout() {
    ctx, cancel := context.WithTimeout(
        context.Background(),
        5 * time.Second,
    )
    defer cancel()
    
    data, err := fetchData(ctx)
    if err != nil {
        fmt.Println("Timeout или ошибка:", err)
        return
    }
    
    fmt.Println("Получены данные:", data)
}

func fetchData(ctx context.Context) (string, error) {
    // Примерно 10 секунд работы
    select {
    case <-time.After(10 * time.Second):
        return "data", nil
    case <-ctx.Done():
        return "", ctx.Err()  // Вернёт context deadline exceeded
    }
}

3. Передача значений через goroutines Контекст может хранить значения, доступные всем goroutines в цепочке вызовов.

type requestID string

const requestIDKey requestID = "requestID"

func handleRequest(parentCtx context.Context, userID string) {
    // Добавляем значения в контекст
    ctx := context.WithValue(parentCtx, requestIDKey, "req-12345")
    
    // Передаём контекст в другие функции
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    // Достаём значение из контекста
    reqID, ok := ctx.Value(requestIDKey).(string)
    if ok {
        fmt.Println("ID запроса:", reqID)
    }
}

Иерархия контекстов

Контексты образуют иерархическое дерево отмены.

context.Background()  ← root контекст
    │
    ├─ WithCancel()     ← можно отменить вручную
    │   └─ WithTimeout() ← отмена через время
    │
    └─ WithTimeout()    ← отмена через время
func main() {
    // Родительский контекст
    parent, parentCancel := context.WithCancel(context.Background())
    defer parentCancel()
    
    // Дочерний контекст наследует отмену
    child, childCancel := context.WithCancel(parent)
    defer childCancel()
    
    // Если отменить parent, отменится и child
    go func() {
        <-parent.Done()
        fmt.Println("Parent отменён, child тоже отменится")
    }()
    
    parentCancel()
    time.Sleep(time.Second)
}

Практический пример: HTTP сервер

import "net/http"

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // r.Context() автоматически отменяется при разрыве соединения
    ctx := r.Context()
    
    // Добавляем timeout
    ctx, cancel := context.WithTimeout(ctx, 30 * time.Second)
    defer cancel()
    
    // Выполняем долгую операцию
    result := expensiveOperation(ctx)
    
    w.Write([]byte(result))
}

func expensiveOperation(ctx context.Context) string {
    done := make(chan string)
    
    go func() {
        // Долгая операция
        time.Sleep(5 * time.Second)
        done <- "результат"
    }()
    
    select {
    case result := <-done:
        return result
    case <-ctx.Done():
        return "Операция отменена или истёк timeout"
    }
}

Best Practices

1. Никогда не сохраняй контекст как поле структуры

// ❌ Плохо
type Handler struct {
    ctx context.Context  // НИКОГДА!
}

// ✅ Хорошо
func (h *Handler) Handle(ctx context.Context) {
    // Передавай как параметр
}

2. Передавай контекст первым параметром

// ✅ Правильный порядок
func processData(ctx context.Context, userID string, data []byte) error {
    // ...
}

// ❌ Неправильный порядок
func processData(userID string, ctx context.Context, data []byte) error {
    // ...
}

3. Проверяй Done канал в цикла обработки

func worker(ctx context.Context, jobs <-chan int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Worker остановлен")
            return
        case job := <-jobs:
            process(job)
        }
    }
}

4. Используй WithValue только для передачи данных, относящихся к запросу

// ✅ Правильное использование
ctx = context.WithValue(ctx, "user_id", 123)
ctx = context.WithValue(ctx, "request_id", "req-456")

// ❌ Неправильное использование
ctx = context.WithValue(ctx, "database_connection", db)  // НЕ передавай ресурсы

Ошибки при работе с Context

// Ошибка 1: игнорирование отмены
func longRunningTask() {
    for {
        // ❌ Не проверяем контекст!
        doSomeWork()
    }
}

// Ошибка 2: забыли вызвать cancel
func badFunction() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    // ❌ забыли: defer cancel()
}

// Ошибка 3: создание контекста на основе значения вместо time.After
func wrong() {
    ctx := context.WithValue(context.Background(), "timeout", "5s")  // ❌
    // Это не создаёт timeout!
}

Реальный пример: параллельные запросы с отменой

func fetchMultipleURLs(ctx context.Context, urls []string) []string {
    results := make([]string, len(urls))
    var wg sync.WaitGroup
    
    for i, url := range urls {
        wg.Add(1)
        go func(i int, url string) {
            defer wg.Done()
            
            select {
            case <-ctx.Done():
                results[i] = "Отменено"
                return
            default:
                data, _ := http.Get(url)
                results[i] = data
            }
        }(i, url)
    }
    
    wg.Wait()
    return results
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
    defer cancel()
    
    results := fetchMultipleURLs(ctx, []string{
        "http://example.com",
        "http://example.org",
        "http://example.net",
    })
    
    fmt.Println(results)
}

Context — это необходимый инструмент для написания конкурентного, надёжного кода на Go. Он позволяет контролировать жизненный цикл goroutines и операций, предотвращая утечки ресурсов и обеспечивая graceful shutdown.

Для чего контекст в Go? | PrepBro