I have two goroutines independently producing data, each sending it to a channel. In my main goroutine, I'd like to consume each of these outputs as they come in, but don't care the order in which they come in. Each channel will close itself when it has exhausted its output. While the select statement is the nicest syntax for consuming inputs independently like this, I haven't seen a concise way for looping over each of until both of the channels have closed.
for { select { case p, ok := <-mins: if ok { fmt.Println("Min:", p) //consume output } case p, ok := <-maxs: if ok { fmt.Println("Max:", p) //consume output } //default: //can't guarantee this won't happen while channels are open // break //ideally I would leave the infinite loop //only when both channels are done } }
the best I can think to do is the following (just sketched, may have compile errors):
for { minDone, maxDone := false, false select { case p, ok := <-mins: if ok { fmt.Println("Min:", p) //consume output } else { minDone = true } case p, ok := <-maxs: if ok { fmt.Println("Max:", p) //consume output } else { maxDone = true } } if (minDone && maxDone) {break} }
But this looks like it would get untenable if you're working with more than two or three channels. The only other method I know of is to use a timout case in the switch statement, which will either be small enough to risk exiting early, or inject too much downtime into the final loop. Is there a better way to test for channels being within a select statement?
Even though we can't send data to a closed channel, we can still read data from a closed channel. Consider the code shown below. package main import "fmt" func main() { ch := make(chan int, 3) ch <- 2 ch <- 3 close(ch) <-ch fmt. Println("Channel closed!") }
The value read from a closed channel will be the zero value of the channel's type. For example, if the channel is an int channel, then the value received from a closed channel will be 0 .
The select statement lets a goroutine wait on multiple communication operations. A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.
Closing a channel indicates that no more values will be sent on it. This can be useful to communicate completion to the channel's receivers.
Your example solution would not work well. Once one of them closed, it would always be available for communication immediately. This means your goroutine will never yield and other channels may never be ready. You would effectively enter an endless loop. I posted an example to illustrate the effect here: http://play.golang.org/p/rOjdvnji49
So, how would I solve this problem? A nil channel is never ready for communication. So each time you run into a closed channel, you can nil that channel ensuring it is never selected again. Runable example here: http://play.golang.org/p/8lkV_Hffyj
for { select { case x, ok := <-ch: fmt.Println("ch1", x, ok) if !ok { ch = nil } case x, ok := <-ch2: fmt.Println("ch2", x, ok) if !ok { ch2 = nil } } if ch == nil && ch2 == nil { break } }
As for being afraid of it becoming unwieldy, I don't think it will. It is very rare you have channels going to too many places at once. This would come up so rarely that my first suggestion is just to deal with it. A long if statement comparing 10 channels to nil is not the worst part of trying to deal with 10 channels in a select.
Close is nice in some situations, but not all. I wouldn't use it here. Instead I would just use a done channel:
for n := 2; n > 0; { select { case p := <-mins: fmt.Println("Min:", p) //consume output case p := <-maxs: fmt.Println("Max:", p) //consume output case <-done: n-- } }
Complete working example at the playground: http://play.golang.org/p/Cqd3lg435y
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With