Worker Pool Pattern in Go - Concurrency Patterns
What is the Worker Pool Pattern?
The Worker Pool Pattern is a design pattern used to manage a pool of worker threads (or goroutines in Go) to handle a set of tasks. The pattern is particularly useful in scenarios where you have a large number of tasks and want to avoid creating and destroying a large number of threads or goroutines frequently. Instead, you use a fixed number of worker threads or goroutines that continuously process tasks from a queue. This approach helps manage resources efficiently and can significantly improve performance by reducing overhead.
Why Use the Worker Pool Pattern?
Resource Management: It controls the number of concurrent tasks and avoids creating an excessive number of goroutines, which can be resource-intensive.
Scalability: It can handle varying loads efficiently by adjusting the number of workers or tasks in the pool.
Performance: Reusing worker goroutines reduces the overhead of creating and destroying goroutines frequently.
Decoupling: It separates task generation from task execution, making your code more modular and maintainable.
Let's walk through a basic implementation of the Worker Pool Pattern in Go.
- Define the Worker and Task
We'll create a worker type and a task channel for workers to process.
package main
import (
"fmt"
"sync"
"time"
)
type Task struct {
ID int
Job func() // Job is the function that will be executed by the worker
}
type Worker struct {
ID int
TaskChannel chan Task
Quit chan bool
}
func NewWorker(id int) Worker {
return Worker{
ID: id,
TaskChannel: make(chan Task),
Quit: make(chan bool),
}
}
func (w Worker) Start(wg *sync.WaitGroup) {
go func() {
defer wg.Done()
for {
select {
case task := <-w.TaskChannel:
fmt.Printf("Worker %d started task %d\n", w.ID, task.ID)
task.Job()
fmt.Printf("Worker %d finished task %d\n", w.ID, task.ID)
case <-w.Quit:
fmt.Printf("Worker %d stopping\n", w.ID)
return
}
}
}()
}
- Create a Pool and Manage Workers
We'll define a pool of workers and a mechanism to submit tasks to the pool.
type WorkerPool struct {
Workers []Worker
TaskQueue chan Task
Quit chan bool
}
func NewWorkerPool(numWorkers int) WorkerPool {
pool := WorkerPool{
Workers: make([]Worker, numWorkers),
TaskQueue: make(chan Task),
Quit: make(chan bool),
}
for i := 0; i < numWorkers; i++ {
worker := NewWorker(i)
pool.Workers[i] = worker
}
return pool
}
func (p *WorkerPool) Start() {
var wg sync.WaitGroup
wg.Add(len(p.Workers))
for _, worker := range p.Workers {
worker.Start(&wg)
}
go func() {
for {
select {
case task := <-p.TaskQueue:
worker := p.Workers[task.ID % len(p.Workers)]
worker.TaskChannel <- task
case <-p.Quit:
for _, worker := range p.Workers {
worker.Quit <- true
}
wg.Wait()
close(p.TaskQueue)
return
}
}
}()
}
func (p *WorkerPool) Stop() {
close(p.Quit)
}
- Using the Worker Pool
Here’s how you can use the worker pool to process tasks.
func main() {
pool := NewWorkerPool(3) // Create a pool with 3 workers
pool.Start()
for i := 0; i < 10; i++ {
taskID := i
pool.TaskQueue <- Task{
ID: taskID,
Job: func() {
time.Sleep(2 * time.Second) // Simulate work
fmt.Printf("Task %d completed\n", taskID)
},
}
}
time.Sleep(10 * time.Second) // Give some time for tasks to complete
pool.Stop() // Stop the worker pool
}
Use Cases and Where to Use the Worker Pool Pattern
Web Servers: Handling incoming requests with a pool of worker goroutines can efficiently manage load and avoid creating excessive threads.
Batch Processing: Processes like image or video processing where tasks are independent and can be parallelized.
Data Processing: Handling large-scale data processing tasks such as log processing or ETL (Extract, Transform, Load) jobs.
Concurrency Control: Managing concurrency in applications where tasks can be parallelized, but you need to limit the number of concurrent operations for resource management.
Summary
The Worker Pool Pattern is a powerful way to manage a large number of tasks with a controlled number of goroutines, leading to efficient resource usage and improved performance. By using this pattern, you can ensure that your application handles tasks effectively without overwhelming system resources.
Subscribe to my newsletter
Read articles from Sundaram Kumar Jha directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sundaram Kumar Jha
Sundaram Kumar Jha
I Like Building Cloud Native Stuff , the microservices, backends, distributed systemsand cloud native tools using Golang