← Назад к вопросам
Как устроен контекст?
2.0 Middle🔥 211 комментариев
#Конкурентность и горутины#Основы Go
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Устройство контекста (Context) в Go
Что такое контекст?
Context — это интерфейс в пакете context, который предоставляет способ для отмены операций, установки таймаутов и передачи значений между горутинами и API.
package context
type Context interface {
// Deadline возвращает время, когда контекст отменится
Deadline() (deadline time.Time, ok bool)
// Done возвращает канал, закрывающийся при отмене
Done() <-chan struct{}
// Err возвращает причину отмены (если она произошла)
Err() error
// Value возвращает значение для ключа
Value(key interface{}) interface{}
}
Основные типы контекстов
1. Background Context
Корневой контекст, который никогда не отменяется:
ctx := context.Background()
// Используется как основа для других контекстов
// Никогда не отменяется, нет deadline, нет значений
2. TODO Context
Используется, когда точно неизвестен нужный контекст:
ctx := context.TODO()
// Сигнализирует, что контекст ещё не определён
// По сути такой же как Background
3. Context с Timeout/Deadline
WithTimeout — отмена через определённый промежуток времени:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // Обязательно отменить контекст
// Контекст отменится через 5 секунд ИЛИ при вызове cancel()
result, err := doSomethingWithTimeout(ctx)
WithDeadline — отмена в конкретное время:
deadline := time.Now().Add(30 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
4. Context с Cancel
WithCancel — отмена вручную:
ctx, cancel := context.WithCancel(context.Background())
go func() {
// Долгая операция
time.Sleep(2 * time.Second)
// Отменить контекст
cancel()
}()
// Ждём отмены
<-ctx.Done()
fmt.Println("Контекст отменён")
5. Context с Values
WithValue — передача значений:
type RequestID string
const requestIDKey RequestID = "requestID"
ctx := context.WithValue(context.Background(), requestIDKey, "req-123")
// Получить значение
id := ctx.Value(requestIDKey).(string)
fmt.Println("ID запроса:", id)
Практический пример: HTTP запрос с timeout
func fetchUserData(userID int) (*User, error) {
// Создаём контекст с таймаутом 5 секунд
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Создаём HTTP запрос с контекстом
req, err := http.NewRequestWithContext(ctx, "GET",
fmt.Sprintf("https://api.example.com/users/%d", userID), nil)
if err != nil {
return nil, err
}
// Выполняем запрос
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, fmt.Errorf("request timeout")
}
return nil, err
}
defer resp.Body.Close()
var user User
json.NewDecoder(resp.Body).Decode(&user)
return &user, nil
}
Каскадные контексты (Context Tree)
parentCtx, parentCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer parentCancel()
// Дочерний контекст наследует deadline родителя
childCtx, childCancel := context.WithTimeout(parentCtx, 5*time.Second)
defer childCancel()
// childCtx отменится ПЕРВЫМ (его deadline раньше)
// При отмене parentCtx автоматически отменяются все childCtx
Обработка отмены контекста
func doWork(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Работа отменена:", ctx.Err())
return
default:
// Выполняем работу
fmt.Println("Работаю...")
time.Sleep(1 * time.Second)
}
}
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
go doWork(ctx)
defer cancel()
time.Sleep(5 * time.Second)
Лучшие практики
✅ Правильно:
// Передавайте контекст первым параметром
func FetchData(ctx context.Context, id int) (*Data, error) {
// Используйте ctx для timeout/cancel
// Проверяйте ctx.Done() в циклах
}
// Не забывайте вызывать cancel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// Проверяйте ошибку контекста
if ctx.Err() != nil {
log.Println("Context error:", ctx.Err())
}
❌ Неправильно:
// Не создавайте контекст в функции, если он передан параметром
func Handler(ctx context.Context) {
newCtx := context.Background() // ❌ Теряется родительский контекст
doWork(newCtx)
}
// Не игнорируйте cancel
ctx, _ := context.WithTimeout(...) // ❌ Утечка ресурсов
// Не используйте контекст для передачи всех значений
ctx = context.WithValue(ctx, "user_db", userDB) // ❌ Для конфига используй DI
Вывод
Контекст в Go — это элегантный способ управления жизненным циклом операций, установки таймаутов и отмены. Основные операции: timeout, deadline, cancel, values. Всегда проверяйте ctx.Done() и не забывайте вызывать cancel().