← Назад к вопросам
Таймаут для горутины
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 можно расширить для обработки нескольких источников данных