Lesson 7 / 25
select Basics and Non-blocking Operations
Multiplexing channel operations.
How select chooses
A select statement lists channel sends and receives as cases. It blocks until at least one case can proceed, then runs that case. If several are ready at the same time, it picks one uniformly at random, so there is no priority by order of appearance and no case is starved in the long run. A default clause runs immediately when no case is ready, which turns the select into a non-blocking operation: try to send, and if the buffer is full, drop or count the item instead. An empty select {} blocks forever. Inside a for loop, select becomes an event loop that reacts to whichever channel fires next.
Waiting on several things at once
select lets one goroutine wait on many channel operations, including timers and cancellation signals.
Non-blocking send and receive with default
Dropping events when a buffer is full instead of stalling.
package main
import "fmt"
func main() {
events := make(chan string, 1)
dropped := 0
for _, e := range []string{"a", "b", "c"} {
select {
case events <- e:
// queued
default:
dropped++ // buffer full: do not block the caller
}
}
fmt.Println("dropped:", dropped) // 2: only "a" fitted
select {
case e := <-events:
fmt.Println("got", e)
default:
fmt.Println("nothing ready")
}
}Avoid busy loops with default
A for { select { ... default: } } loop with nothing blocking in default spins a CPU core at 100%. Use default for one-shot non-blocking attempts, not as a polling loop.
Quick check: If two cases of a select are ready at the same time, which runs?
- One chosen pseudo-randomly
- Always the first one written
- Always the last one written
- Both, in order
Answer
One chosen pseudo-randomly — Random choice among ready cases prevents systematic starvation; do not rely on case order for priority.