Запросам нужен общий context: первая ошибка отменяет остальные. Для указанной сигнатуры вызов должен быть err := download(...), без второго результата.
Параллельная загрузка списка URL с ранним завершением при ошибке
Запросам нужен общий context: первая ошибка отменяет остальные. Для указанной сигнатуры вызов должен быть err := download(...), без второго результата.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Дан массив строк, каждая из которых представляет собой URL. Требуется написать функцию download(urls []string) error, которая будет одновременно скачивать содержимое всех указанных адресов. Если хотя бы один запрос завершится с ошибкой, функция должна немедленно вернуть эту ошибку; если все запросы прошли успешно — вернуть nil. Необходимо обеспечить корректную работу в многопоточной среде и реализовать механизм прекращения всех остальных запросов при возникновении первой ошибки.
func main() {
_, err := download([]string{
"https://example.com/e25e26d3-6aa3-4d79-9ab4-fc9b71103a8c.xml",
"https://example.com/a601590e-31c1-424a-8ccc-dec5fb35c0f6.xml",
"https://example.com/1cf0dd69-a3e5-4682-84e3-dfe22ca771f4.xml",
"https://example.com/ceb566f2-a234-4cb8-9466-4a26f1363aa8.xml",
"https://example.com/b6ed16d7-cb3d-4cba-b81a-01a789d3a914.xml",
})
if err != nil {
panic(err)
}
}
func download(urls []string) error {
return nil
}
Ответ
Запросам нужен общий context: первая ошибка отменяет остальные. Для указанной сигнатуры вызов должен быть err := download(...), без второго результата.
Ниже скачивание означает чтение и отбрасывание тела; для сохранения файлов нужен отдельный контракт. Буфер результатов позволяет завершать горутины после раннего возврата.
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func download(urls []string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client := &http.Client{Timeout: 30 * time.Second}
results := make(chan error, len(urls))
for _, url := range urls {
go func(url string) {
results <- func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { return err }
resp, err := client.Do(req)
if err != nil { return err }
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("HTTP status %d", resp.StatusCode)
}
_, err = io.Copy(io.Discard, resp.Body)
return err
}()
}(url)
}
for range urls {
if err := <-results; err != nil {
return err
}
}
return nil
}
Функция возвращает при получении первой ошибки и отменяет запросы, не ожидая завершения всех горутин. Фактическая остановка транспорта кооперативная. Для большого списка добавляют ограничение параллелизма.