Putting a channel case first does not give it priority. A Go select waits for a communication that can proceed; if several cases are ready, it chooses one by uniform pseudo-random selection, not source order. This avoids favoring a continuously ready channel, but makes case-order assumptions a source of subtle bugs. section 9.4
for {
select { #1
case v := <-messageCh: #2
fmt.Println(v)
case <-disconnectCh: #3
fmt.Println("disconnection, return")
return
}
}
Here, placing messageCh before disconnectCh does not ensure that all buffered messages are processed. A producer can send ten messages and then signal disconnection, yet the loop may print only five before returning. section 9.4
for {
select {
case v := <-messageCh:
fmt.Println(v)
case <-disconnectCh:
for { #1
select {
case v := <-messageCh: #2
fmt.Println(v)
default: #3
fmt.Println("disconnection, return")
return
}
}
}
}
If disconnection means “process messages already available, then return,” enter a second select loop after receiving the notification. Its default case runs when no message receive can proceed. This drains currently available messages; it does not wait for messages that might arrive later. section 9.4
A for/select loop also needs a plan for closed channels. Receiving from a closed channel remains ready and returns its element type’s zero value. In a two-channel merge, ignoring closure can therefore publish zeros repeatedly instead of finishing. Use the two-result receive, `v, open := <-ch`, to distinguish a value from closure. section 9.6
func merge(ch1, ch2 <-chan int) <-chan int {
ch := make(chan int, 1)
go func() {
for ch1 != nil || ch2 != nil { #1
select {
case v, open := <-ch1:
if !open {
ch1 = nil #2
break
}
ch <- v
case v, open := <-ch2:
if !open {
ch2 = nil #3
break
}
ch <- v
}
}
close(ch)
}()
return ch
}
After a receive reports `open == false`, setting that input channel to nil removes its receive case from consideration. The loop can keep forwarding from the other input and close the output once both inputs are nil. section 9.6
func f(ctx context.Context) error {
// ...
select { #1
case <-ctx.Done():
return ctx.Err()
case ch1 <- struct{}{}:
}
select { #2
case <-ctx.Done():
return ctx.Err()
case v := <-ch2:
// ...
}
}
When a function accepts a context, put potentially blocking sends and receives in separate selects alongside `ctx.Done()`. Cancellation can then provide a way out instead of leaving the function blocked on a channel operation. section 8.6.4








