Lesson 15 · Concurrency & context
Channels & select
"Share memory by communicating" — how goroutines actually cooperate.
Your win: send and receive on channels, tell buffered from
unbuffered, and use select — especially the ctx.Done() case
that makes a goroutine cancellable.
A channel is a typed conduit
ch := make(chan int) // unbuffered
ch := make(chan int, 8) // buffered, capacity 8
ch <- v // send
v := <-ch // receive
v, ok := <-ch // ok == false if channel closed & drained
close(ch) // sender closes; never the receiver
for v := range ch { ... } // receive until closed
Go's motto: "Don't communicate by sharing memory; share memory by communicating." Instead of guarding a shared variable with a lock, pass the value down a channel — ownership moves with it, and the send/receive is itself synchronised.1
select: wait on several channels at once
select blocks until one of its cases can proceed; if several are ready it
picks one at random; a default makes it non-blocking. The most important
case in production Go is ctx.Done() — it's how a goroutine honours
cancellation:
select {
case msg := <-work:
process(msg)
case <-ctx.Done(): // cancelled/timed out → stop cleanly
return ctx.Err()
}
Our consumer loops use exactly this to back off and stay cancellable —
selecting between a retry timer and the context's Done()
(golibs/kafka/kafka.go:672-771). That's how
Close() can stop a wedged consumer instead of leaking it (Lesson 14).
Your fan-in, decoded
The clearest channel example in our services collects results from two parallel repo calls:
conversationmgmt/.../support_conversation_reader_staff_get_conversations.go:48-93errChan := make(chan error, 2) // buffered = #producers → sends never block
go func() { errChan <- repoA(...) }()
go func() { errChan <- repoB(...) }()
go func() { wg.Wait(); close(errChan) }() // closer: signal "all done"
for err := range errChan { multierr.Append(...) } // aggregate
Wait() cleanly ends the
range. "Share memory by communicating," in your codebase.
close — and only
once. These show up constantly in interviews.
Rob Pike — Go Concurrency Patterns (slides)
Channels as a composition tool — generators, fan-in, pipelines. Then the Tour's channels & select pages for hands-on.
→ go.dev/talks/2012/concurrency.slide
→ A Tour of Go — channels & select
Check yourself (from memory)
Q1. A send on an unbuffered channel…
Q2. select is used to…
default makes it non-blocking.
Q3. Adding case <-ctx.Done(): to a select lets a goroutine…
wg.Wait(); close(errChan), then the main goroutine ranges
the channel and aggregates with multierr. The channel transfers each value
safely (send happens-before receive) — no shared variable, no mutex. "Share memory by
communicating."1. Go Concurrency Patterns; Effective Go — channels; Go Proverbs.