Concurrency Patterns
Three foundational patterns
Section titled “Three foundational patterns”Go’s goroutines and channels compose naturally into a small set of well-understood patterns. Once you recognise them, you can combine them to build arbitrarily complex concurrent systems.
Pipeline
Section titled “Pipeline”A pipeline is a series of stages connected by channels. Each stage receives values from an upstream channel, transforms them, and sends results to a downstream channel. Stages run concurrently — while stage 2 is processing item N, stage 1 is already working on item N+1.
generate → square → printPipelines are ideal for stream processing where each transformation is independent and the stages can be composed like Unix pipes.
Fan-out / fan-in
Section titled “Fan-out / fan-in”Fan-out starts multiple goroutines that read from the same input channel, parallelising work. Fan-in merges multiple output channels into one for the consumer to read. Together they implement a parallel processing stage inside a pipeline.
Worker pool
Section titled “Worker pool”A worker pool is a fixed set of goroutines that all consume from a shared jobs channel. The pool size caps resource usage (goroutines, connections, file handles) while still processing work concurrently. It is the Go equivalent of a thread pool.
main → jobs channel → [worker 1, worker 2, worker 3] → resultsThe runnable example below combines all three patterns: a pipeline with a generator and a squaring stage, followed by a worker pool that processes a batch of jobs deterministically.
package main
import ( "fmt" "sync")
// --- Pipeline ---
func generate(nums ...int) <-chan int { out := make(chan int, len(nums)) for _, n := range nums { out <- n } close(out) return out}
func square(in <-chan int) <-chan int { out := make(chan int, cap(in)) go func() { for n := range in { out <- n * n } close(out) }() return out}
// --- Worker pool ---
func workerPool(jobs <-chan int, results []int, wg *sync.WaitGroup) { for j := range jobs { results[j-1] = j * 2 wg.Done() }}
func main() { // Pipeline: 1,2,3,4,5 -> squares nums := generate(1, 2, 3, 4, 5) squares := square(nums) for v := range squares { fmt.Println(v) }
// Worker pool: 3 workers, 5 jobs, each job doubles its index const numJobs = 5 jobs := make(chan int, numJobs) results := make([]int, numJobs) var wg sync.WaitGroup
for w := 0; w < 3; w++ { go workerPool(jobs, results, &wg) } for j := 1; j <= numJobs; j++ { wg.Add(1) jobs <- j } close(jobs) wg.Wait()
for _, r := range results { fmt.Println(r) }}Loading Go runtime (first run only, ~8 MB)…