Комментарии (2)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование context.WithTimeout
context.WithTimeout — ключевой механизм для установки дедлайнов в Go-приложениях. Он создает контекст, который автоматически отменяется по истечении заданного времени, что критически важно для предотвращения "подвешивания" операций и управления ресурсами.
Базовый синтаксис
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Создаем контекст с таймаутом 2 секунды
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Важно: освобождаем ресурсы
// Передаем контекст в функцию
doWork(ctx)
}
func doWork(ctx context.Context) {
select {
case <-time.After(3 * time.Second):
fmt.Println("Работа завершена")
case <-ctx.Done():
fmt.Println("Отменено по таймауту:", ctx.Err())
}
}
Практические паттерны использования
1. HTTP-запросы с таймаутом
func fetchWithTimeout(url string, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return "", err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
2. База данных и внешние сервисы
func queryDatabase(ctx context.Context, query string) ([]Result, error) {
// Устанавливаем таймаут для конкретной операции
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// Контекст передается в драйвер БД
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("ошибка запроса: %w", err)
}
defer rows.Close()
// Обработка результатов
var results []Result
for rows.Next() {
// ...
}
return results, rows.Err()
}
3. Каскадирование таймаутов
func processWithCascadingTimeouts() error {
// Родительский контекст с общим таймаутом
parentCtx, parentCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer parentCancel()
// Дочерние контексты с более короткими таймаутами
dataCtx, dataCancel := context.WithTimeout(parentCtx, 3*time.Second)
defer dataCancel()
logCtx, logCancel := context.WithTimeout(parentCtx, 1*time.Second)
defer logCancel()
// Параллельное выполнение с разными таймаутами
var wg sync.WaitGroup
wg.Add(2)
go fetchData(dataCtx, &wg)
go writeLog(logCtx, &wg)
wg.Wait()
return nil
}
Критически важные аспекты
Всегда вызывайте cancel()
- Функция
cancelосвобождает ресурсы контекста - Используйте
defer cancel()сразу после создания контекста - Невызов
cancelможет привести к утечкам памяти
// ПРАВИЛЬНО:
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// ОПАСНО (потенциальная утечка):
ctx, _ := context.WithTimeout(context.Background(), time.Second)
Проверка типа ошибки
func handleOperation(ctx context.Context) error {
resultChan := make(chan Result)
go performTask(resultChan)
select {
case res := <-resultChan:
return res
case <-ctx.Done():
// Анализ причины отмены
switch ctx.Err() {
case context.DeadlineExceeded:
return fmt.Errorf("превышен дедлайн")
case context.Canceled:
return fmt.Errorf("операция отменена")
default:
return fmt.Errorf("неизвестная ошибка: %w", ctx.Err())
}
}
}
Распространенные ошибки
-
Переиспользование отмененного контекста
// Неправильно ctx, cancel := context.WithTimeout(context.Background(), time.Second) cancel() // ctx теперь отменен - не используйте его для новых операций -
Игнорирование контекста в горутинах
// Опасный код go func() { // Если ctx отменяется, эта горутина может зависнуть time.Sleep(10 * time.Second) process(data) }() // Правильно go func(ctx context.Context) { select { case <-time.After(10 * time.Second): process(data) case <-ctx.Done(): return // Выход при отмене } }(ctx)
Оптимизация производительности
// Используйте пул таймеров для частых операций
var timerPool = sync.Pool{
New: func() interface{} {
return time.NewTimer(time.Second)
},
}
func withOptimizedTimeout(ctx context.Context, d time.Duration) (context.Context, func()) {
timer := timerPool.Get().(*time.Timer)
timer.Reset(d)
ctx, cancel := context.WithCancel(ctx)
stop := func() {
cancel()
if timer.Stop() {
timerPool.Put(timer)
}
}
go func() {
select {
case <-timer.C:
cancel()
case <-ctx.Done():
stop()
}
}()
return ctx, stop
}
Заключение
context.WithTimeout — мощный инструмент для управления временем выполнения операций в Go. Ключевые принципы:
- Всегда используйте
defer cancel() - Проверяйте
ctx.Done()в долгих операциях - Правильно обрабатывайте
context.DeadlineExceeded - Каскадируйте контексты для сложных операций
- Не передавайте отмененные контексты в новые операции
Правильное использование контекстов с таймаутами делает приложения более устойчивыми, предсказуемыми и эффективными в использовании ресурсов.