Реализуйте пул воркеров, который сам подстраивает число рабочих горутин под
нагрузку. Когда задач накапливается много, пул запускает дополнительных
воркеров (но не более maxWorkers); когда нагрузка спадает, лишние
простаивающие воркеры завершаются (но всегда остаётся не менее minWorkers).
type ScalingPool struct {
// ...
}
// NewScalingPool создаёт пул, читающий задачи из канала tasks. Число
// воркеров динамически меняется в диапазоне [minWorkers, maxWorkers]
// в зависимости от давления очереди.
func NewScalingPool(minWorkers, maxWorkers int, tasks <-chan func()) *ScalingPool
// Start запускает пул (минимальное число воркеров + менеджер масштабирования).
func (p *ScalingPool) Start()
// Stop корректно останавливает все воркеры и ожидает их завершения.
func (p *ScalingPool) Stop()
// Peak возвращает максимальное число одновременно работавших воркеров.
func (p *ScalingPool) Peak() int
Требования:
- В любой момент времени активно не более
maxWorkers и не менее
minWorkers воркеров.
- При росте бэклога задач пул масштабируется вверх; простаивающие лишние
воркеры завершаются сами.
- Каждая задача из
tasks выполняется ровно один раз.
Stop() завершает все горутины без утечек.
Peak() отражает пиковое число одновременных воркеров (через atomic).
На что смотрит интервьюер:
- Корректное управление жизненным циклом горутин: запуск/останов воркеров,
отсутствие утечек после
Stop() (проверка через runtime.NumGoroutine).
- Безопасное масштабирование: лишние воркеры завершаются, но граница
minWorkers соблюдается, верхняя граница maxWorkers не превышается.
- Отсутствие гонок под
-race; каждая задача выполнена ровно один раз
(атомарный счётчик).
- Использование
sync.WaitGroup, atomic-счётчиков и каналов/контекста для
координации без блокировок и дедлоков.