Нужен пул, который «дышит» вместе с нагрузкой. Минимум воркеров, minWorkers,
живёт всегда. Когда задач много, пул временно добавляет воркеров, но не больше
maxWorkers. Когда стало тихо, добавленные воркеры сами уходят после
idleTimeout простоя.
func NewElasticPool(minWorkers, maxWorkers int, idleTimeout time.Duration, tasks <-chan func()) *ElasticPool
// Start запускает пул.
func (p *ElasticPool) Start()
// Stop останавливает пул и ждёт завершения воркеров.
func (p *ElasticPool) Stop()
// Peak возвращает наибольшее число одновременно живых воркеров за всё время.
func (p *ElasticPool) Peak() int
// Active возвращает текущее число живых воркеров.
func (p *ElasticPool) Active() int
Требования:
- Задачи берутся из канала
tasks. Сразу после Start живы ровно minWorkers воркеров.
- Если все воркеры заняты, а задача ждёт, пул запускает дополнительного воркера. Живых воркеров при этом никогда не больше
maxWorkers.
- Дополнительный воркер, который
idleTimeout не получал задач, завершается. Воркеры из первых minWorkers не завершаются по простою, поэтому пул не опускается ниже minWorkers.
Peak и Active можно вызывать из любой горутины в любой момент.
Stop не принимает новых задач и ждёт, пока завершатся воркеры и уже выполняющиеся функции. Задачи, оставшиеся в канале tasks, не выполняются. Хотите выполнить всё, дождитесь завершения задач до вызова Stop.
- Код безопасен под
go test -race.
Пример:
tasks := make(chan func())
p := NewElasticPool(2, 8, 50*time.Millisecond, tasks)
p.Start()
fmt.Println(p.Active()) // 2
for i := 0; i < 20; i++ {
tasks <- func() { time.Sleep(20 * time.Millisecond) }
}
fmt.Println(p.Peak() > 2 && p.Peak() <= 8) // true: пул раздулся
time.Sleep(200 * time.Millisecond) // тишина дольше idleTimeout
fmt.Println(p.Active()) // 2: лишние воркеры ушли
p.Stop()
Тесты проверяют: сразу после Start Active() равно minWorkers; во время нагрузки Active() всегда в границах [min, max], а число одновременно выполняющихся функций не больше maxWorkers и больше minWorkers; Peak() согласуется с реальным максимумом; после простоя Active() становится ровно minWorkers и не опускается ниже; Stop дожидается выполняющейся функции, после него Active() == 0; при maxWorkers < minWorkers максимумом считается минимум.
Допущения. Start вызывают один раз. Функции задач в итоге возвращаются.
Если maxWorkers < minWorkers, считайте максимумом minWorkers.
Что проверяют на собеседовании: границы [min, max] под гонками, таймер
простоя и чистая остановка.