81 lines
2.3 KiB
Markdown
81 lines
2.3 KiB
Markdown
---
|
||
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,英国,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.
|