Ограниченный пул горутин для выполнения GET‑запросов

Создайте фиксированные три работника и канал заданий. После окончания генератора обязательно закройте очередь и дождитесь работников: исходный main иначе завершит процесс раньше ответов.

Короткий ответ

Что ответить на собеседовании

Создайте фиксированные три работника и канал заданий. После окончания генератора обязательно закройте очередь и дождитесь работников: исходный 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, печать одного ответа не перемешивается с другим. Порядок ответов зависит от завершения запросов; адреса из условия здесь не запрашивались.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее