Создайте фиксированные три работника и канал заданий. После окончания генератора обязательно закройте очередь и дождитесь работников: исходный main иначе завершит процесс раньше ответов.
Ограниченный пул горутин для выполнения GET‑запросов
Создайте фиксированные три работника и канал заданий. После окончания генератора обязательно закройте очередь и дождитесь работников: исходный main иначе завершит процесс раньше ответов.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо обеспечить запуск не более трёх одновременно работающих воркеров, которые делают HTTP‑GET запросы к URL‑адресам из предоставленного списка и выводят тело ответа в стандартный вывод.
package main
import (
"log"
)
// worker pool:
// организовать запуск не более 3 параллельных воркеров,
// которые выполняют запросы на все url из массива urls
// и выполнят GET‑запрос на каждый URL.
// Вывести body ответа в stdout в текстовом виде.
// пример урлов, которые получаем от генератора:
//
// http://echo.xff.pw:8080/?echo_time=100&echo_body=body-1
// http://echo.xff.pw:8080/?echo_time=1500&echo_body=body-2
// http://echo.xff.pw:8080/?echo_time=500&echo_body=body-3
// http://echo.xff.pw:8080/?echo_time=150&echo_body=body-4
// http://echo.xff.pw:8080/?echo_time=500&echo_body=body-5
// функцию main желательно никак не менять,
// но если требуется по реализации — можно
func main() {
pool := makePool(3)
for url := range generateUrls(5) {
log.Printf("=> Запуск, %s", url)
pool.DoWork(url)
}
}
Ответ
Создайте фиксированные три работника и канал заданий. После окончания генератора обязательно закройте очередь и дождитесь работников: исходный main иначе завершит процесс раньше ответов.
Ниже нужны fmt, io, log, net/http, sync, time. generateUrls остаётся генератором из условия:
type Pool struct {
jobs chan string
wg sync.WaitGroup
output sync.Mutex
client *http.Client
}
func makePool(n int) *Pool {
if n < 1 { panic("worker count must be positive") }
p := &Pool{jobs: make(chan string), client: &http.Client{Timeout: 5*time.Second}}
for i := 0; i < n; i++ {
p.wg.Add(1)
go func() {
defer p.wg.Done()
for url := range p.jobs {
body, err := p.fetch(url)
p.output.Lock()
if err != nil { log.Printf("%s: %v", url, err) } else {
fmt.Printf("%s\n", body)
}
p.output.Unlock()
}
}()
}
return p
}
func (p *Pool) fetch(url string) ([]byte, error) {
resp, err := p.client.Get(url)
if err != nil { return nil, err }
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
const limit = 1 << 20
body, err := io.ReadAll(io.LimitReader(resp.Body, limit+1))
if err != nil { return nil, err }
if len(body) > limit { return nil, fmt.Errorf("body too large") }
return body, nil
}
func (p *Pool) DoWork(url string) { p.jobs <- url }
func (p *Pool) Close() {
close(p.jobs)
p.wg.Wait()
}
В main сразу после makePool(3) добавьте defer pool.Close(). Close вызывается один раз после окончания всех DoWork; параллельное закрытие и отправка не допускаются этим контрактом. Очередь даёт backpressure, тело ограничено 1 MiB, печать одного ответа не перемешивается с другим. Порядок ответов зависит от завершения запросов; адреса из условия здесь не запрашивались.