Given this code that simulates fetching of some website-stuff for 3 URL's using a fan-in pattern and range over the condensed channel: https://play.golang.org/p/MSkRI7x4vz
for s := range r {
println(s)
}
This works well, but I want to use an overall timeout signal channel, so I try to use a select in a for-loop like this: https://play.golang.org/p/LjDoIc0j-z
totalTimeout := time.After(300 * time.Millisecond)
loop:
for {
select {
case s := <-r:
fmt.Println(s)
case <-totalTimeout: // signaling usage of a channel
fmt.Println("Timed out")
break loop
}
}
}
This behaves not well: After the input channels are closed, the condensed channel from fan-in is closed. But now zero-values are read until the timeout effectively occurs. When I read http://www.tapirgames.com/blog/golang-channel or the specs https://golang.org/ref/spec#Close it seems that receiving from a closed channel "never blocks" and thus gives back zero values. I can only guess, that a range detects the close, where the select cannot, because it's condition is a read from a channel in itself.
I could break out like this
case s := <-r:
if s == "" {
break loop
}
fmt.Println(s)
Is there another approach to stop getting empty values or is this the proper idiomatic way using a select in a for loop?
Receive on a closed channel yields the zero value after all elements in the channel are received.
Use the two value receive to detect when the channel is closed and break from the loop. The second value is false when the channel yields a zero value because the channel is closed.
for {
select {
case s, ok := <-r:
if !ok {
break loop
}
fmt.Println(s)
case <-totalTimeout: // signaling usage of a channel
fmt.Println("Timed out")
break loop
}
}