Step 1 of 6 · Reading · ~4 min
Learn
Concurrency Patterns
select and Timeouts
After this lesson you can wait on several channels at once, put a deadline on any wait, stop goroutines with one close, and serve one channel ahead of another.
In Channels every receive waited on exactly one channel. select waits on several and runs the first case that can proceed.
One case runs
Output: gave up after 50ms. time.After(d) returns a channel that receives one value after d, so a timeout is one more case. The buffer of 1 lets the late goroutine finish its send instead of blocking forever.
select blocks until some case can proceed; if several can, it picks one at random, so never rely on case order. Adding a default case makes it non-blocking: default runs at once when no other case is ready (a default in a loop with nothing else to wait on spins a CPU core at 100%).
select inside a loop
Output: ticks: 3 (at about 50, 100 and 150 ms). A Ticker delivers on C every period until you Stop it; a Timer fires once.
The break trap. break inside a select leaves only the select, not the loop around it:
To leave the loop, label the for (loop: on the line before it) and write break loop, which would print just got 5, or return.
Closed channels and nil channels
A closed channel is always ready: its receive returns at once with ok == false. Operations on a nil channel block forever, so setting the variable to nil switches its case off. Fan-in merges two channels into one and uses this to finish:
With a carrying 1, 2, 3 and b carrying 10, 20, ranging over merge(a, b) yields all five values in some interleaving (the sum is always 36) and ends when both inputs close.
Stopping goroutines: the done channel
struct{} is a type with no data, for when only the signal matters. close(done) makes <-done ready for every waiting goroutine, forever; a send would wake only one. (context.Context in Go Advanced wraps this idea and adds deadlines.)
Priority
A plain select treats ready cases as equals. To prefer a channel, try it alone first:
With jobs holding job-1, job-2 and control holding pause, three trips through this print control: pause, job: job-1, job: job-2; a plain two-case select would print a job first about half the time. It is deterministic only because everything was queued beforehand; in a live system priority is best-effort.
Common mistakes
- One select over all channels for priority. Two ready channels are chosen between at random, so items come out in the wrong order on some runs.
- Not setting a closed channel to nil. It stays ready, and the loop keeps handling empty zero values instead of ending.
breakin a loop's select. It leaves the select only, so the loop keeps running.- Using
defaultwhen you mean a timeout.defaultanswers instantly, so a value arriving 10 ms later is reported as missing.
Your exercise
Write drain, which serves an urgent channel ahead of a normal one ahead of a low one, and awaitLate, which waits a bounded time for one straggler. The starter parses the input and prints each served item. The likeliest mistake is re-checking urgent only once, instead of before every item.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…