← Go, End to End

Go, End to End

Concurrency Patterns

Goroutines and channels are primitives. This chapter is the handful of patterns built from those primitives that show up constantly in real Go code: running a fixed pool of workers, combining several producers into one stream, and cleanly cancelling work that's no longer needed.

Worker pools

A worker pool runs a fixed, bounded number of goroutines pulling jobs off a shared channel, instead of spawning one goroutine per job (which, under enough load, is exactly the unbounded-goroutine risk flagged in the previous chapter).

go · live, editable, runnable

Notice the channel directions in the signature: jobs <-chan int means worker can only receive from jobs, results chan<- int means it can only send to results. The compiler enforces this, a worker literally cannot accidentally send on jobs or receive from results, it's a real, checked contract, not just a naming convention.

Fan-in / fan-out

Fan-out is starting multiple goroutines to process work in parallel from a shared source. Fan-in is merging multiple result channels back into a single channel. The worker pool above is fan-out; here's the fan-in half, standalone, combining two independent producers into one stream.

go · live, editable, runnable

context.Context for cancellation

`context.Context` is the idiomatic way to propagate "stop now" (or "stop after this deadline") through a call chain, especially one that spans goroutines. A function that does long-running or blocking work should accept a `Context` as its first parameter and check it periodically, so a caller further up the chain can cancel everything below it with one call.

go · live, editable, runnable

`context.WithTimeout` (and its sibling `context.WithCancel`) returns both a context and a cancel function. Calling `cancel()`, or letting the timeout elapse, closes the context's internal Done channel, which every goroutine watching that context sees immediately via `<-ctx.Done()`. This is how you cancel an entire tree of goroutines with a single function call, rather than manually threading a stop signal through every layer yourself.

sync.WaitGroup

A `WaitGroup` is a counter: `Add(n)` increments it by `n`, `Done()` decrements it by one (almost always called via `defer` right at the top of the goroutine), and `Wait()` blocks until it hits zero. It's the standard way to wait for a known, fixed number of goroutines to finish, seen already in both examples above.

go
var wg sync.WaitGroup
wg.Add(1)          // one goroutine to wait for
go func() {
	defer wg.Done() // always defer this, right after starting the goroutine's real work
	// ... do work ...
}()
wg.Wait()          // blocks until Done() has been called once per Add()

Picking the right tool

Worker pools bound concurrency for a stream of independent jobs. Fan-in/fan-out combine or split streams of work. WaitGroup answers "wait for these N things to finish". Context answers "stop everything if the caller no longer wants the result", which frequently gets combined with a WaitGroup: context cancels the work, WaitGroup confirms every goroutine actually noticed and exited.