Как устроены контексты в языке Go?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Контекст (context.Context) в Go используется для передачи информации о дедлайнах, отмене и других данных между API границами. Он представляет собой дерево: каждый контекст является дочерним по отношению к другому, кроме корневого (context.Background() или context.TODO()).
Основные методы интерфейса context.Context:
Deadline() (deadline time.Time, ok bool): Возвращает дедлайн контекста.Done() <-chan struct{}: Возвращает канал, который закрывается при отмене контекста.Err() error: Возвращает ошибку, которая привела к отмене контекста.Value(key any) any: Возвращает значение, связанное с ключом в контексте.
Создаются с помощью функций пакета context:
context.Background(): Корневой пустой контекст, используется как основа для главного процесса.context.TODO(): Используется как заглушка, когда неизвестно, какой контекст использовать.context.WithCancel(parent Context): Создает дочерний контекст и функцию отмены.context.WithDeadline(parent Context, d time.Time): Создает дочерний контекст с дедлайном.context.WithTimeout(parent Context, timeout time.Duration): Создает дочерний контекст с таймаутом.context.WithValue(parent Context, key, val any): Создает дочерний контекст со значением.
Основные реализации Context:
emptyCtx: Базовый, не отменяемый и без значений. ИспользуетсяBackgroundиTODO.cancelCtx: Поддерживает отмену. Состоит из базового контекста и канала отмены.timerCtx: Поддерживает дедлайн/таймаут. СодержитcancelCtxи таймер.valueCtx: Поддерживает передачу значений. Содержит базовый контекст и ключ-значение.
При отмене родительского контекста автоматически отменяются все дочерние. Это позволяет распространять сигналы отмены или дедлайны по цепочке вызовов.
Пример использования context.WithCancel:
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Создаем корневой контекст и функцию отмены
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // Гарантируем отмену при выходе из main
go func() {
select {
case <-ctx.Done(): // Ожидаем отмены контекста
fmt.Println("Задача отменена:", ctx.Err())
return
case <-time.After(5 * time.Second): // Или завершаем через 5 секунд
fmt.Println("Задача выполнена")
}
}()
// Отмена контекста из другого места
time.Sleep(1 * time.Second)
fmt.Println("Отмена контекста...")
cancel()
// Даем время горутине обработать отмену
time.Sleep(100 * time.Millisecond)
}
Пример использования context.WithValue:
package main
import (
"context"
"fmt"
)
type userIDKey string
func processRequest(ctx context.Context) {
if userID, ok := ctx.Value(userIDKey("UserID")).(string); ok {
fmt.Println("Processing request for User ID:", userID)
} else {
fmt.Println("User ID not found in context")
}
}
func main() {
ctx := context.WithValue(context.Background(), userIDKey("UserID"), "12345")
processRequest(ctx)
}
Контексты потокобезопасны и неизменяемы, что важно для concurrent кода. Они не предназначены для передачи необработанных параметров функции, а скорее для метаинформации, влияющей на выполнение процесса.