NOTE
2.6 Select
How Go `select` waits on multiple channel operations, chooses among ready cases, uses `default`, handles nil channels, timeouts, and cancellation.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What Is select?
select lets one goroutine wait on multiple channel send/receive operations.
select {
case v := <-c1:
fmt.Println("c1", v)
case c2 <- value:
fmt.Println("sent")
default:
fmt.Println("nothing ready")
}
At a high level:
- if one communication can proceed, that case runs;
- if multiple cases can proceed, one ready case is selected using runtime pseudo-randomized selection rather than source-code priority;
- if none can proceed and
defaultexists,defaultruns; - if none can proceed and there is no
default, the goroutine blocks.
2. Nil Channels
Sending to or receiving from a nil channel blocks forever. This property is useful inside select: setting a channel variable to nil dynamically disables that case.
3. Empty Select
select {}
An empty select blocks forever. If every goroutine in a program becomes permanently blocked and the runtime detects that no progress is possible, the program can terminate with a deadlock error.
4. Timeouts
For one-off timeouts:
select {
case result := <-resultCh:
_ = result
case <-time.After(5 * time.Second):
return
}
For repeated loops, prefer a reusable time.Timer/time.Ticker where appropriate instead of repeatedly allocating timers through time.After.
For request-scoped cancellation, context.Context is usually the better abstraction.
5. Exiting a for-select
A plain break inside a select exits the select, not the surrounding for. Common choices are:
return;- a labeled
break; - cancellation that causes the surrounding function to return.
6. Runtime Internals
Compiler/runtime implementation differs by case shape. Complex selects ultimately use runtime selection machinery (historically runtime.selectgo). Treat those symbols as version-specific; the language semantics above are the stable part.