Add MinWorkers option
This commit is contained in:
@@ -16,6 +16,16 @@ func defaultPanicHandler(panic interface{}) {
|
|||||||
fmt.Printf("Worker exits from a panic: %v\nStack trace: %s\n", panic, string(debug.Stack()))
|
fmt.Printf("Worker exits from a panic: %v\nStack trace: %s\n", panic, string(debug.Stack()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func linearGrowthFn(workerCount, minWorkers, maxWorkers int) int {
|
||||||
|
if workerCount < minWorkers {
|
||||||
|
return minWorkers
|
||||||
|
}
|
||||||
|
if workerCount < maxWorkers {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// Option represents an option that can be passed when building a worker pool to customize it
|
// Option represents an option that can be passed when building a worker pool to customize it
|
||||||
type Option func(*WorkerPool)
|
type Option func(*WorkerPool)
|
||||||
|
|
||||||
@@ -33,8 +43,16 @@ func PanicHandler(panicHandler func(interface{})) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MinWorkers allows to change the minimum number of workers of a worker pool
|
||||||
|
func MinWorkers(minWorkers int) Option {
|
||||||
|
return func(pool *WorkerPool) {
|
||||||
|
pool.minWorkers = minWorkers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WorkerPool models a pool of workers
|
// WorkerPool models a pool of workers
|
||||||
type WorkerPool struct {
|
type WorkerPool struct {
|
||||||
|
minWorkers int
|
||||||
maxWorkers int
|
maxWorkers int
|
||||||
maxCapacity int
|
maxCapacity int
|
||||||
idleTimeout time.Duration
|
idleTimeout time.Duration
|
||||||
@@ -45,6 +63,7 @@ type WorkerPool struct {
|
|||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
waitGroup sync.WaitGroup
|
waitGroup sync.WaitGroup
|
||||||
panicHandler func(interface{})
|
panicHandler func(interface{})
|
||||||
|
growthFn func(int, int, int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a worker pool with that can scale up to the given number of workers and capacity
|
// New creates a worker pool with that can scale up to the given number of workers and capacity
|
||||||
@@ -58,6 +77,7 @@ func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
|
|||||||
dispatchedTasks: make(chan func(), maxWorkers),
|
dispatchedTasks: make(chan func(), maxWorkers),
|
||||||
purgerQuit: make(chan struct{}),
|
purgerQuit: make(chan struct{}),
|
||||||
panicHandler: defaultPanicHandler,
|
panicHandler: defaultPanicHandler,
|
||||||
|
growthFn: linearGrowthFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply all options
|
// Apply all options
|
||||||
@@ -65,6 +85,14 @@ func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
|
|||||||
opt(pool)
|
opt(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure options are consistent
|
||||||
|
if pool.maxWorkers <= 0 {
|
||||||
|
pool.maxWorkers = 1
|
||||||
|
}
|
||||||
|
if pool.minWorkers > pool.maxWorkers {
|
||||||
|
pool.minWorkers = pool.maxWorkers
|
||||||
|
}
|
||||||
|
|
||||||
// Start dispatcher goroutine
|
// Start dispatcher goroutine
|
||||||
pool.waitGroup.Add(1)
|
pool.waitGroup.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -81,6 +109,11 @@ func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
|
|||||||
pool.purge()
|
pool.purge()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Start minWorkers workers
|
||||||
|
if pool.minWorkers > 0 {
|
||||||
|
pool.startWorkers()
|
||||||
|
}
|
||||||
|
|
||||||
return pool
|
return pool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +171,12 @@ func (p *WorkerPool) dispatch() {
|
|||||||
// Attempt to submit the task to a worker without blocking
|
// Attempt to submit the task to a worker without blocking
|
||||||
case p.dispatchedTasks <- task:
|
case p.dispatchedTasks <- task:
|
||||||
if p.Running() == 0 {
|
if p.Running() == 0 {
|
||||||
p.startWorker()
|
p.startWorkers()
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Create a new worker if we haven't reached the limit yet
|
// Create a new worker if we haven't reached the limit yet
|
||||||
if p.Running() < p.maxWorkers {
|
if p.Running() < p.maxWorkers {
|
||||||
p.startWorker()
|
p.startWorkers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block until a worker accepts this task
|
// Block until a worker accepts this task
|
||||||
@@ -167,7 +200,7 @@ func (p *WorkerPool) purge() {
|
|||||||
select {
|
select {
|
||||||
// Timed out waiting for any activity to happen, attempt to stop an idle worker
|
// Timed out waiting for any activity to happen, attempt to stop an idle worker
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if p.Running() > 0 {
|
if p.Running() > p.minWorkers {
|
||||||
select {
|
select {
|
||||||
case p.dispatchedTasks <- nil:
|
case p.dispatchedTasks <- nil:
|
||||||
default:
|
default:
|
||||||
@@ -182,13 +215,22 @@ func (p *WorkerPool) purge() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *WorkerPool) startWorker() {
|
func (p *WorkerPool) startWorkers() {
|
||||||
|
|
||||||
|
count := p.growthFn(p.Running(), p.minWorkers, p.maxWorkers)
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Increment worker count
|
// Increment worker count
|
||||||
atomic.AddInt32(&p.workerCount, 1)
|
atomic.AddInt32(&p.workerCount, int32(count))
|
||||||
|
|
||||||
// Increment waiting group semaphore
|
// Increment waiting group semaphore
|
||||||
p.waitGroup.Add(1)
|
p.waitGroup.Add(count)
|
||||||
|
|
||||||
|
//go func() {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
worker(p.dispatchedTasks, func() {
|
worker(p.dispatchedTasks, func() {
|
||||||
|
|
||||||
// Decrement worker count
|
// Decrement worker count
|
||||||
@@ -198,6 +240,9 @@ func (p *WorkerPool) startWorker() {
|
|||||||
p.waitGroup.Done()
|
p.waitGroup.Done()
|
||||||
|
|
||||||
}, p.panicHandler)
|
}, p.panicHandler)
|
||||||
|
}
|
||||||
|
//}()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop causes this pool to stop accepting tasks, without waiting for goroutines to exit
|
// Stop causes this pool to stop accepting tasks, without waiting for goroutines to exit
|
||||||
|
|||||||
+81
-3
@@ -11,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
taskCount = 1000000
|
taskCount = 10000
|
||||||
taskDuration = 10 * time.Millisecond
|
taskDuration = 1 * time.Millisecond
|
||||||
workerCount = 200000
|
workerCount = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkPond(b *testing.B) {
|
func BenchmarkPond(b *testing.B) {
|
||||||
@@ -36,6 +36,26 @@ func BenchmarkPond(b *testing.B) {
|
|||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkPondMinWorkers(b *testing.B) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
pool := pond.New(workerCount, taskCount, pond.MinWorkers(workerCount))
|
||||||
|
defer pool.StopAndWait()
|
||||||
|
|
||||||
|
// Submit tasks
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
wg.Add(taskCount)
|
||||||
|
for i := 0; i < taskCount; i++ {
|
||||||
|
pool.Submit(func() {
|
||||||
|
time.Sleep(taskDuration)
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkPondGroup(b *testing.B) {
|
func BenchmarkPondGroup(b *testing.B) {
|
||||||
pool := pond.New(workerCount, taskCount)
|
pool := pond.New(workerCount, taskCount)
|
||||||
defer pool.StopAndWait()
|
defer pool.StopAndWait()
|
||||||
@@ -72,6 +92,64 @@ func BenchmarkGoroutines(b *testing.B) {
|
|||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkGoroutinePool(b *testing.B) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Submit tasks
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
taskChan := make(chan func())
|
||||||
|
wg.Add(workerCount)
|
||||||
|
// Start worker goroutines
|
||||||
|
for i := 0; i < workerCount; i++ {
|
||||||
|
go func() {
|
||||||
|
for task := range taskChan {
|
||||||
|
task()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// Submit tasks
|
||||||
|
for i := 0; i < taskCount; i++ {
|
||||||
|
taskChan <- func() {
|
||||||
|
time.Sleep(taskDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(taskChan)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBufferedGoroutinePool(b *testing.B) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Submit tasks
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
taskChan := make(chan func(), taskCount)
|
||||||
|
wg.Add(workerCount)
|
||||||
|
// Start worker goroutines
|
||||||
|
for i := 0; i < workerCount; i++ {
|
||||||
|
go func() {
|
||||||
|
for task := range taskChan {
|
||||||
|
task()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// Submit tasks
|
||||||
|
for i := 0; i < taskCount; i++ {
|
||||||
|
taskChan <- func() {
|
||||||
|
time.Sleep(taskDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(taskChan)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkGammazeroWorkerpool(b *testing.B) {
|
func BenchmarkGammazeroWorkerpool(b *testing.B) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wp := workerpool.New(workerCount)
|
wp := workerpool.New(workerCount)
|
||||||
|
|||||||
+28
-2
@@ -209,7 +209,7 @@ func TestSubmitWithPanic(t *testing.T) {
|
|||||||
assert.Equal(int32(1), atomic.LoadInt32(&doneCount))
|
assert.Equal(int32(1), atomic.LoadInt32(&doneCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubmitWithIdleTimeout(t *testing.T) {
|
func TestPoolWithCustomIdleTimeout(t *testing.T) {
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ func TestSubmitWithIdleTimeout(t *testing.T) {
|
|||||||
pool.StopAndWait()
|
pool.StopAndWait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubmitWithPanicHandler(t *testing.T) {
|
func TestPoolWithCustomPanicHandler(t *testing.T) {
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
@@ -264,6 +264,32 @@ func TestSubmitWithPanicHandler(t *testing.T) {
|
|||||||
assert.Equal("panic now!", capturedPanic)
|
assert.Equal("panic now!", capturedPanic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPoolWithCustomMinWorkers(t *testing.T) {
|
||||||
|
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
pool := pond.New(10, 5, pond.MinWorkers(10))
|
||||||
|
|
||||||
|
// Submit a task that panics
|
||||||
|
started := make(chan struct{})
|
||||||
|
completed := make(chan struct{})
|
||||||
|
pool.Submit(func() {
|
||||||
|
<-started
|
||||||
|
completed <- struct{}{}
|
||||||
|
})
|
||||||
|
|
||||||
|
started <- struct{}{}
|
||||||
|
|
||||||
|
// 10 workers should have been started
|
||||||
|
assert.Equal(10, pool.Running())
|
||||||
|
|
||||||
|
<-completed
|
||||||
|
|
||||||
|
pool.StopAndWait()
|
||||||
|
|
||||||
|
assert.Equal(0, pool.Running())
|
||||||
|
}
|
||||||
|
|
||||||
func TestGroupSubmit(t *testing.T) {
|
func TestGroupSubmit(t *testing.T) {
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user