Select
A receive like <-ch commits you to one channel. Real programs juggle
several — results from two workers, a data channel plus a "stop" channel.
select is Go's way to wait on many channel operations and act on whichever
is ready first:
select {
case msg := <-emails:
fmt.Println("email:", msg)
case msg := <-alerts:
fmt.Println("alert:", msg)
}
It looks like switch, but each case is a channel send or receive. select
blocks until one case can proceed, runs that case's body, and moves on.
The rest are abandoned — nothing is consumed from the channels that didn't
fire.
Ties are broken randomly
If several cases are ready at the same moment, select picks one at
random — deliberately. Always preferring the first case would starve the
others under load; the randomness keeps every ready channel flowing.
The consequence for you: never write code that depends on which case wins a
tie, and never write tests that assume an order between channels. (Your
exercise's tests accept values in any order for exactly this reason.)
default: don't wait at all
Add a default case and select stops blocking — if no channel is ready,
default runs immediately:
select {
case v := <-results:
fmt.Println("got", v)
default:
fmt.Println("nothing yet, doing something else")
}
This is the idiom for a non-blocking receive (or send). It turns "wait for a value" into "check for a value", useful when a goroutine has other work to interleave — or, as you'll see next lesson, when it should poll "have I been cancelled?" between steps.
Timeouts
time.After(d) returns a channel that delivers one value after d. Put it
in a select and you have a timeout:
select {
case v := <-results:
fmt.Println("got", v)
case <-time.After(2 * time.Second):
fmt.Println("gave up after 2s")
}
Whichever happens first wins: a result within two seconds, or the timeout.
This pattern is fine for one-off waits; for cancellation that must ripple
through a whole call tree — a web request abandoned by its client, say —
Go has a dedicated tool, context, coming two lessons from now.
Draining channels that close: the nil trick
One more tool for the exercise. Receiving from a closed channel succeeds
instantly (with ok == false), so a closed channel in a select fires over
and over, drowning out the live cases. Receiving from a nil channel, on
the other hand, blocks forever — which inside a select simply means "this
case is disabled". So the idiom for merging streams until all are done is:
when a channel reports closed, set that variable to nil, and loop until
every channel is nil:
case v, ok := <-a:
if !ok {
a = nil // a is done; never fires again
continue
}
out <- v
Combine that with what you know about closing, and you can build merge —
the classic fan-in — in the exercise below.
Practice
1. Merge Two Channels
AdvancedImplement
merge, the classic fan-in: combine two input streams into one.func merge(a, b <-chan int) <-chan int- Every value received from
aand every value received frombmust be emitted on the returned channel, in any order — the tests sort what they collect before comparing. - Close the returned channel when both inputs are closed and drained — no sooner (values would be lost) and no later (readers would block forever).
- Values must flow as they arrive: if
ais slow, values frombmust still get through. Drainingacompletely before touchingbis wrong — and will deadlock the streaming test.
Two known-good shapes, both covered in the hints:
- One goroutine with a
selectover both channels, using the nil-channel trick from the lesson to disable a channel once it closes. - One goroutine per input copying into the output, plus a
sync.WaitGroupwhoseWaitgates a singleclose.
Pick either. The contract — everything from both, output closed exactly once, after both inputs finish — is what's graded.
package main import "fmt" // merge emits every value from a and b on the returned channel (in any // order), closing it once both inputs are closed and drained. func merge(a, b <-chan int) <-chan int { // TODO: fan both inputs into one output channel; close the output // only when BOTH inputs are done. out := make(chan int) close(out) return out } func main() { a := make(chan int, 2) a <- 1 a <- 3 close(a) b := make(chan int, 2) b <- 2 b <- 4 close(b) for v := range merge(a, b) { fmt.Println(v) } }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- Every value received from