Module lessons (3/5)
`select`: multiplexing channels
select waits on multiple channel operations at the same time.
It is the switch of Go concurrency: it lets you react to the first
channel ready among many.
Syntax
select {
case v := <-ch1:
fmt.Println("ch1:", v)
case v := <-ch2:
fmt.Println("ch2:", v)
case ch3 <- 99:
fmt.Println("inviato su ch3")
case <-time.After(time.Second):
fmt.Println("timeout")
default:
fmt.Println("nessuno è pronto")
}Semantics:
- All cases are evaluated; only one of the ready cases is chosen.
- If multiple cases are ready, the choice is pseudo-random (no ordering).
- If no case is ready: the
selectblocks until one becomes ready. - If there is a
default: it runs immediately when no case is ready (non-blocking select).
Timeout pattern
select {
case res := <-fetch(url):
use(res)
case <-time.After(2 * time.Second):
log.Println("timeout: fetch troppo lento")
}time.After(d) returns a <-chan Time that produces a value after d.
It is the classic way to bound the wait.
Non-blocking select with default
select {
case msg := <-ch:
process(msg)
default:
// niente da fare, prosegui
}It "polls" the channel without blocking. Useful in code paths that must stay responsive without mandatorily waiting.
"Done channel" pattern
Combine select with a cancellation channel:
func worker(in <-chan Job, done <-chan struct{}) {
for {
select {
case j := <-in:
process(j)
case <-done:
return // chi gestisce done segnala lo shutdown
}
}
}close(done) from the coordinator triggers case <-done in every
waiting goroutine: coordinated shutdown.
Loop with select
for {
select {
case v := <-input:
if v == nil {
return
}
handle(v)
case <-time.After(5 * time.Second):
keepAlive()
case <-ctx.Done():
return
}
}The typical "event loop" pattern for servers and workers.
A nil channel as a "disabled" case
An advanced trick: a case <-ch with ch == nil is never
selected. It lets you "disable" a case dynamically:
var in chan int = source()
for {
select {
case v, ok := <-in:
if !ok {
in = nil // disabilita questo case
continue
}
handle(v)
case <-ctx.Done():
return
}
}When in is closed, I null it: the select keeps handling only the
other cases.
Try it
Implement a select with two cases that receive from ch1 and ch2; print which one arrived first.
Show hint
Syntax `select { case ...: ... }` with one case per channel.
Solution available after 3 attempts
Add a timeout case to the select using time.After(time.Second).
Show hint
`time.After(d)` is a channel that receives after d.
Solution available after 3 attempts
What makes a select non-blocking?
select { case v := <-ch: ... ??? }Recap
selectwaits for the first ready case among several channel operations.- Pseudo-random choice among multiple ready cases.
default→ non-blocking select (polling).time.After(d)for timeouts (mind the leak in loops: usecontext).- Patterns: done channel, event loop, nil channel to disable a case.
- Empty
select{}= eternal block: almost always a bug.