How does select choose among multiple ready cases? Is it fair?
If more than one case is ready, select picks one uniformly at random (pseudo-random). Source order gives no priority. The runtime (selectgo) shuffles the poll order with cheaprand and locks the channels in address order to avoid deadlocks between concurrent selects. If no case is ready and there is a default, the default runs. Otherwise the goroutine parks on every channel at once and wakes on whichever becomes ready first.
The randomness prevents starvation: a constantly busy first case can't shut out the others. It also means that you can't rely on case order for priority.
// Common bug: expecting quit to "win"
for {
select {
case v := <-data: // may keep getting picked even after quit is closed
handle(v)
case <-quit:
return
}
}
If both are ready, each iteration has a 50% chance of processing another data item. That is usually fine, but when you need strict "stop now" behavior, add a priority check (see the next question). Also, all channel and value expressions in every case are evaluated once, in source order, on entry to select, before a case is chosen.
More on Channels & select
- Q188Who should close a channel, and why? What are the rules?
- Q189How do you safely close a channel that might be closed by multiple goroutines?
- Q191How do you implement a priority select in Go?
- Q192Why is a nil channel useful in a
select? Show an example. - Q193What does this print? (select with a closed channel and a nil channel)
- Q194What causes "fatal error: all goroutines are asleep - deadlock!" and when does Go fail to detect a deadlock?