Stage 1 · Code
Concurrency in Practice
Worker Pools
When you have far more jobs than you want running at once, a worker pool gives you controlled concurrency: enough parallelism to make progress, but with a clear upper limit.
Why Limit Concurrency
Suppose you need to process 10,000 images, API requests, or files. Launching 10,000 goroutines is possible, but that does not automatically mean it is wise. The real bottleneck may be a database connection pool, an external API rate limit, CPU, memory, or simply how much simultaneous work your machine can handle without becoming noisy and slow.
A worker pool is the pattern for this situation. Instead of starting one goroutine per job with no limit, you create a fixed number of workers. All workers read from the same jobs channel. That means at most that many jobs are in progress at once, no matter how long the queue becomes.
You might still have 10,000 jobs overall. The pool only answers one question: how many are allowed to run concurrently right now? That is what protects downstream systems and your own process from being overwhelmed.
Building the Pool
The moving parts are straightforward once you see their roles clearly. The jobs channel is the queue of work to be done. Each worker goroutine loops over that channel and processes jobs one by one. A results channel carries completed outputs back. A WaitGroup tracks when all workers have exited, and only then do we close the results channel.
- Start a fixed number of workers up front.
- Feed jobs into one shared jobs channel.
- Each worker ranges over that channel until it closes.
- Use a
WaitGroupto know when every worker is finished. - Only the coordinating goroutine closes the results channel, and only after all sends are done.
Tracing Three Workers and Six Jobs
The exact worker-to-job mapping is nondeterministic because it depends on scheduling. That is normal. What is deterministic is the high-level behavior: no job is processed more than once, every submitted job is eventually handled, and no more than three jobs are active at a time because the pool only has three workers.
The goroutine that sends all jobs should close jobs because it owns job production. The coordinating goroutine that waits for all workers should close results because it knows when no more results can possibly be sent.
Quiz and Practice
What is the main reason to use a worker pool instead of starting one goroutine per job with no limit?
Hands-On Project
Build a small worker pool that processes many numeric jobs safely, but returns one stable summary value so the harness does not care which worker handled which job.
Write a function `SumSquaresWithPool(workerCount int, jobs []int) int` that uses a worker pool. Each worker should square the jobs it receives and send the squared value to a results channel. Return the sum of all squared results after every job has been processed.
Summary and Key Takeaways
- Use a worker pool when you have many jobs but need a fixed upper bound on concurrent work.
- All workers read from the same jobs channel, so the queue distributes work naturally.
- A
WaitGrouptells you when every worker is finished. - Close
jobsfrom the sender side and closeresultsonly after all workers are done sending. - The exact worker-to-job assignment may vary, but the concurrency limit and overall correctness should not.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.