Files
Hugo/content/blog/go-concurrency.md
T
fukunee 1fb7959071 Standardize poet attributions and reformat blog template
Convert all poet attribution lines to use an em dash, English
name format, and consistent spacing. Re-indent the blog single
page template for readability and adjust the typewriter element
layout.
2026-05-14 02:29:10 +08:00

81 lines
2.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Go Concurrency Patterns in Practice"
date: 2026-05-15T10:00:00+08:00
draft: false
poet: |
To see a World in a Grain of Sand
And a Heaven in a Wild Flower
— William Blake, UK, 1757–1827
description: "Go concurrency beyond the basics. Worker pools for bounded parallelism, context propagation for cancellation, select loops for channel coordination — three patterns that hold up in production."
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.