Why does this loop spin at 100% CPU after one channel closes?
Question 496HardGo 1.22 to 1.25
func merge(a, b <-chan int, out chan<- int) {
for {
select {
case v := <-a:
out <- v
case v := <-b:
out <- v
}
}
}
A receive from a closed channel is always ready and returns the zero value right away. Once a closes, select keeps choosing that case, and the loop floods out with zeros. It also never ends.
Fix: use the comma-ok form and set a finished channel to nil. A nil channel blocks forever, so its case can never be chosen again:
func merge(a, b <-chan int, out chan<- int) {
defer close(out)
for a != nil || b != nil {
select {
case v, ok := <-a:
if !ok {
a = nil
continue
}
out <- v
case v, ok := <-b:
if !ok {
b = nil
continue
}
out <- v
}
}
}
The nil-channel trick is the idiomatic way to turn off a select case. Also mention adding a case <-ctx.Done(): return so the goroutine can be cancelled.
More on Tricky Output & Code-Review Puzzles
- Q494Spot the bug: why is err nil after the if block?
- Q495Find the two WaitGroup bugs.
- Q497What does this select print, and where does the break go?
- Q498What does each channel operation do on nil, open, and closed channels?
- Q499Which of these deadlock, and why does Go detect some deadlocks but not others?
- Q500Is msg guaranteed to print "hello"? Compare unbuffered and buffered channels.