Anton Zhiyanov’s mini-book covers goroutines, channels, select, pipelines, time, context, wait groups, mutexes, semaphores, signaling, pools and atomics in nineteen runnable sections — every example executes in the page, and there is a static PDF in the companion repo. It is explicit about its scope (“a quick refresher on Go concurrency, not a beginner’s guide”) and about its provenance: “The book is AI-free.”
The parts that matter most are the ones where the language’s traps turn out to be designed behaviour:
- Sending on an unbuffered channel is synchronous; the only reason to close a channel is to tell readers all data has been sent, and if no reader needs that, you do not close it. Closing twice, or writing to a closed channel, panics
- Reading a closed buffered channel still drains the buffer before returning the zero value with
ok == false - nil channels block forever on read and write — which is the mechanism, not a footgun: setting a finished input to
nilretires it from aselect, which is how the merge pattern terminates cleanly selectpicks randomly among ready cases, runsdefaultwhen nothing is ready, and otherwise blocks — the basis for merging, cancellation and non-blocking sends that return “busy” instead of blocking- Pipelines are reader → N processors → writer, each stage returning
<-chan Tand closing it withdefer, with done channels for completion and a caller-closed cancel channel selected against the send
The clearest page is the one separating a data race from a race condition:
- A data race is concurrent access with at least one write. It does not reliably panic, and only
-racefinds it; channels are safe by construction - A race condition is an unpredictable interleaving that produces a wrong state even when every individual operation is atomic. The book’s check-then-act withdrawal passes the race detector cleanly and still ends at a balance of
-30 - The consequence is the useful lesson: a clean
-racerun is not evidence of correctness, because the detector reports unsynchronized access, not invariants broken by legal interleavings - The fix is protecting the composite operation, not the individual accesses — mutex, or atomic compare-and-swap where the shape allows. “The composition of atomics is always non-atomic”
The remaining sections are equally unsentimental. sync.Pool is a GC optimization with no size limit — “if you start 1000 more goroutines that call Get at the same time, 1000 more buffers will be allocated” — so it is not a concurrency limit. sync.Cond works, but the book says plainly that channel broadcast carries data and is not one-shot. And diagnostics get real weight: goroutine, block and mutex profiles alongside CPU and heap, go tool trace, and flight recording to keep a rolling recent trace for the bug that will not reproduce.
It is the kind of reference that is worth the twenty minutes because of how few claims it makes. Scope stated, failure modes stated, the better modern option named when it exists, and a pointer to the author’s longer book for anyone who actually needs to learn the material from the ground up.