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

Таймаут для горутины

1.2 Junior🔥 201 комментариев
#Конкурентность и горутины

Условие

Реализуйте функцию, которая выполняет задачу в горутине с таймаутом. Если задача не завершилась за указанное время, возвращается ошибка таймаута.

Сигнатура

func executeWithTimeout(timeout time.Duration, task func() (int, error)) (int, error)

Требования

  • Запустить task в отдельной горутине
  • Если task завершается до таймаута - вернуть её результат
  • Если время истекло - вернуть ошибку "timeout"
  • Использовать select и time.After

Пример

// Успешное выполнение
result, err := executeWithTimeout(2*time.Second, func() (int, error) {
    time.Sleep(1 * time.Second)
    return 42, nil
})
// result = 42, err = nil

// Таймаут
result, err := executeWithTimeout(1*time.Second, func() (int, error) {
    time.Sleep(2 * time.Second)
    return 42, nil
})
// result = 0, err = timeout error

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

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

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

Решение

Анализ задачи

Требования:

  • Выполнить задачу в отдельной горутине с ограничением по времени
  • Вернуть результат, если задача завершилась до таймаута
  • Вернуть ошибку, если время истекло
  • Использовать select и time.After для управления таймаутом

Ключевой паттерн: select с двумя каналами — один для результата, один для таймаута.

Решение

func executeWithTimeout(timeout time.Duration, task func() (int, error)) (int, error) {
    // Канал для результата
    resultChan := make(chan int, 1)
    errorChan := make(chan error, 1)
    
    // Запускаем задачу в горутине
    go func() {
        result, err := task()
        if err != nil {
            errorChan <- err
        } else {
            resultChan <- result
        }
    }()
    
    // select ждёт, что первым завершится: результат или таймаут
    select {
    case result := <-resultChan:
        return result, nil
    case err := <-errorChan:
        return 0, err
    case <-time.After(timeout):
        return 0, errors.New("timeout")
    }
}

Пояснения реализации

1. Буферизированные каналы

  • resultChan и errorChan с буфером на 1 элемент
  • Буфер позволяет горутине отправить результат, даже если никто не слушает (избегаем утечки горутин)

2. Запуск задачи в горутине

  • Задача выполняется асинхронно в отдельной горутине
  • Результат или ошибка отправляются в соответствующий канал

3. Select с тремя случаями

  • resultChan: если задача успешно завершилась, возвращаем результат
  • errorChan: если задача вернула ошибку, возвращаем эту ошибку
  • time.After(timeout): если прошло указанное время, возвращаем ошибку таймаута

4. Порядок выполнения

  • Select блокирует до тех пор, пока один из каналов не будет готов к чтению
  • Первый готовый канал выигрывает, остальные игнорируются

Почему буферизация важна

// ❌ Плохо: небуферизированный канал
go func() {
    resultChan <- result  // Блокируется, если главная горутина уже вышла
}()
select {
case <-time.After(timeout):
    return 0, errors.New("timeout")  // Горутина зависнет в памяти
}

// ✅ Хорошо: буферизированный канал
resultChan := make(chan int, 1)
go func() {
    resultChan <- result  // Не блокируется, отправляет в буфер
}()

Альтернативный вариант с context

В реальных Go приложениях лучше использовать context:

func executeWithTimeout(timeout time.Duration, task func() (int, error)) (int, error) {
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    
    // Канал для результата
    resultChan := make(chan int, 1)
    errorChan := make(chan error, 1)
    
    go func() {
        result, err := task()
        if err != nil {
            errorChan <- err
        } else {
            resultChan <- result
        }
    }()
    
    select {
    case result := <-resultChan:
        return result, nil
    case err := <-errorChan:
        return 0, err
    case <-ctx.Done():
        return 0, ctx.Err()  // context.DeadlineExceeded
    }
}

Этот подход позволяет отменить контекст явно через cancel() и обрабатывать различные причины окончания.

Производительность

  • Горутина не заблокируется: буферизированные каналы гарантируют, что горутина сможет отправить результат
  • Нет утечек памяти: даже при таймауте горутина завершится нормально
  • Гибкость: select можно расширить для обработки нескольких источников данных
Таймаут для горутины | PrepBro