9 Concurrency
Concurrency in Go is presented as a practical way to manage many tasks at once without making programs feel blocked or unresponsive. The chapter explains the difference between concurrency and parallelism, then shows why Go is well suited for both because it gives developers lightweight goroutines and channels while the runtime handles scheduling details. Using an RSS reader as the running example, it demonstrates how a simple synchronous approach can stall on network I/O, and how launching work in goroutines lets multiple feeds be fetched at the same time.
Because concurrent code introduces coordination problems, the chapter then focuses on the tools needed to make it reliable. It covers synchronization with wait groups and error groups so the main program can wait for work to finish and capture failures cleanly, rather than relying on crude timing delays. It also shows how mutexes and read-write mutexes protect shared data such as an article store, how context adds cancellation and timeouts to long-running operations, and how these pieces combine to keep concurrent systems safe, consistent, and easier to control.
The second half broadens into channels and common concurrency patterns. Channels are shown as typed communication paths that can be returned, passed into functions, stored in structs, read from, written to, buffered, and restricted by direction to improve safety. Building on that, the chapter demonstrates select-based coordination, worker pools for limiting load, and fan-out/fan-in for launching parallel work and merging results. It ends with advice to keep concurrency simple, prefer communication over shared memory, close channels properly, limit goroutines when needed, and use concurrency only where it clearly improves responsiveness, throughput, or scalability.
Concurrency vs Parallelism
Channels in Go are like Queues with various capacities
Worker pools ingest incoming messages across multiple processes
Fan Out / Fan In
Summary
- Go’s concurrency model is built around goroutines and channels, making concurrent programming simple and efficient.
- Use goroutines to run functions concurrently without managing threads directly.
- Synchronize goroutines using tools like
WaitGroup, Mutex, and the errgroup package for error handling. - Protect shared data with mutexes to avoid race conditions and ensure data consistency.
- Use context.Context to manage cancellation, timeouts, and request-scoped values in concurrent operations.
- Channels provide safe communication between goroutines and enable patterns like pipelines, worker pools, and fan-in/fan-out.
- Buffered and directional channels help control data flow and improve throughput.
- Worker pools and fan-in/fan-out are common concurrency patterns for scalable, efficient processing.
- The select statement allows a goroutine to wait on multiple channels, enabling responsive and robust event loops.
- Always close channels when done and handle errors explicitly to avoid resource leaks and silent failures.
- Use concurrency when tasks can run independently, especially for I/O-bound, network, or parallelizable workloads.
- Avoid unnecessary concurrency in simple, strictly ordered, or single-task programs to keep code maintainable.
FAQ
What is the difference between concurrency and parallelism in Go?
Concurrency is about structuring a program so it can handle multiple tasks at once by switching between them, while parallelism means actually executing multiple tasks at the same time, usually on different CPU cores.When should I use concurrency in a Go program?
Use concurrency when your program has independent tasks that can run separately, such as handling multiple network requests, processing files, doing background work, or keeping an application responsive while waiting on I/O.Why is the go keyword important in Go concurrency?
The go keyword starts a function as a goroutine, allowing it to run concurrently with the rest of the program. This is the basic way to launch asynchronous work in Go.Why did the goroutine example not print anything at first?
Because themain function exited before the goroutine finished. When main() returns, the program ends and any running goroutines are stopped.Why is using time.Sleep not a good way to wait for goroutines?
time.Sleep is unreliable because it guesses how long work will take. A better approach is to use synchronization tools like sync.WaitGroup or errgroup.Group.What does a sync.WaitGroup do?
A WaitGroup waits for a group of goroutines to finish. You add to the counter when starting work, call Done() when each goroutine completes, and call Wait() to block until all work is done.How is errgroup different from WaitGroup?
errgroup.Group works like a WaitGroup, but it also collects errors from goroutines. If any goroutine returns an error, Wait() returns that error after the goroutines finish.
Go in Action, Second Edition ebook for free