Concurrency desk sheet
Choose the tool
by its job
Goroutine for work. Channel for communication. WaitGroup for completion. Mutex for shared state. Select for competing events.
Ten small patterns
Use → guarantee → common trap
| # | Pattern | What it gives you | Common trap |
|---|---|---|---|
| 1 | wg.Go(f) + wg.Wait() | Start one task and join it. | Letting main exit before work finishes. |
| 2 | Several wg.Go calls | Completion of all tasks, not task order. | Assuming launch order is finish order. |
| 3 | Unbuffered chan T | A direct sender/receiver handoff. | Sending and receiving sequentially in one goroutine. |
| 4 | Buffered chan T | Limited decoupling up to capacity. | Treating capacity as a deadlock cure. |
| 5 | close(ch) + range ch | A clear “no more values” signal. | Receiver closes a sender-owned channel. |
| 6 | chan<- T / <-chan T | Compile-time send/receive ownership. | Every stage tries to close every channel. |
| 7 | select + timeout | Wait for the first available event. | Late sender blocks after the receiver leaves. |
| 8 | Jobs → workers → results | Bounded parallel processing. | Closing results before every sender exits. |
| 9 | sync.Mutex | Exclusive access to one invariant. | Locking writes but not reads. |
| 10 | sync.RWMutex | Concurrent readers, exclusive writers. | Using it without a measured need. |
Rules worth memorizing
Ownership, lifetime, ordering
Ownership
- The goroutine that knows no more values will be sent closes the channel.
- Closing is optional when receivers already know how many values to receive.
- Never close merely to release a blocked sender; its next send panics.
Lifetime
- Every goroutine needs a stop condition.
WaitGroupjoins tasks; it does not carry results or errors.- A sleep changes timing, not correctness.
Ordering
- A matching channel operation can establish ordering.
- An
Unlockcan order a laterLock. - No synchronization edge means no visibility guarantee.
Closed channel
Receives drain buffered values, then return the zero value with ok == false. A send panics.
Nil channel
Send and receive block forever. A nil channel disables its case inside select.
Copied lock
Do not copy a mutex or a value containing one after first use. Prefer pointer receiver methods.
Run and verify
Three commands
# Run one
go run ./examples/concurrency/05-close-and-range
# Compile and execute tests for every package
go test ./...
# Run an example with runtime race instrumentation
go run -race ./examples/concurrency/09-mutex-counter
The race detector is dynamic: it can detect only races exercised by the run.