Worker Pools in Go
In the previous lessons, you learned about goroutines, channels, mutexes, and synchronization. In this lesson, you will learn how to combine these concepts to build a powerful and efficient Worker Pool in Go.
Worker pools are widely used in production systems to control concurrency, optimize resource usage, and process tasks efficiently.
What Is a Worker Pool?
A worker pool is a design pattern where:
- A fixed number of worker goroutines are created
- Tasks are sent to them through a channel
- Each worker processes tasks one by one
Instead of creating unlimited goroutines, worker pools provide controlled concurrency.
Why Worker Pools Are Important
Uncontrolled goroutines can lead to:
- High memory usage
- CPU exhaustion
- System instability
Worker pools help maintain predictable performance in systems like:
- API servers
- Background job processors
- File processing pipelines
- Task queues
Basic Worker Pool Structure
A typical worker pool consists of:
- A jobs channel
- A results channel
- Multiple worker goroutines
Simple Worker Function
Each worker listens for incoming jobs and processes them.
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
results <- job * 2
}
}
Each job is doubled and sent to the results channel.
Creating a Worker Pool
Now let’s create a full worker pool with multiple workers.
package main
import "fmt"
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Println("Worker", id, "processing job", job)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 5)
results := make(chan int, 5)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for r := 1; r <= 5; r++ {
fmt.Println("Result:", <-results)
}
}
How This Works
- Three workers wait for jobs
- Five jobs are sent to the jobs channel
- Workers pick jobs concurrently
- Results are collected safely
Real-World Example: Image Processing Queue
Imagine a system that processes uploaded images:
- Jobs = image IDs
- Workers = image processors
- Results = processed image IDs
Worker pools prevent server overload when many images are uploaded simultaneously.
Controlling the Number of Workers
The number of workers should be based on:
- CPU cores
- Memory limits
- Task complexity
For CPU-bound tasks, fewer workers are recommended. For I/O-bound tasks, more workers may be efficient.
Buffered vs Unbuffered Channels
Buffered channels allow sending jobs without blocking immediately.
jobs := make(chan int, 10)
This improves throughput when job production is faster than processing.
Common Mistakes
- Forgetting to close the jobs channel
- Using too many workers
- Not collecting results properly
Practice Exercises
Exercise 1
Create a worker pool that squares numbers instead of doubling them.
Exercise 2
Increase the number of workers and observe how processing speed changes.
Key Takeaways
- Worker pools manage concurrency efficiently
- They prevent resource exhaustion
- Used heavily in production Go systems
- Combine goroutines and channels effectively
What’s Next?
In the next lesson, you will learn about the Context Package and how it helps manage timeouts, cancellations, and request lifecycles in concurrent Go programs.