--- title: "Go Concurrency Patterns in Practice" date: 2026-05-15T10:00:00+08:00 draft: false tags: - Go - Concurrency - Programming --- I've been writing Go for a few years now, and concurrency remains one of those topics where textbook knowledge doesn't quite prepare you for production. Here are a few patterns I've found genuinely useful. ## The Worker Pool The classic. Useful when you need bounded parallelism: ```go func workerPool(jobs <-chan Job, results chan<- Result, count int) { var wg sync.WaitGroup for i := 0; i < count; i++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { results <- process(job) } }() } wg.Wait() close(results) } ``` ## Context Propagation Probably the single most important practice I've adopted: **every long-running goroutine should accept a context**. It makes cancellation and timeout handling explicit rather than an afterthought. ```go func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } // ... } ``` ## The Select Loop When you need to coordinate multiple channels, `select` is your friend. A common pattern: ```go for { select { case msg := <-messages: handle(msg) case <-ctx.Done(): return ctx.Err() case <-ticker.C: flush() } } ``` ## Things I Wish I Knew Earlier 1. **Channel ownership** — the goroutine that writes should also close the channel 2. **Buffered channels aren't a fix for deadlocks** — they just push the problem further out 3. **`sync.WaitGroup` copies by value** — pass it by pointer, always 4. **The race detector is free** — `go test -race` should be part of your CI Concurrency in Go is *easy to write* but *hard to get right*. The compiler won't save you from logical races. That's on us.