Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

breaking out of a select statement when all channels are closed

Tags:

go

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?

like image 578
matthias Avatar asked Dec 02 '12 03:12

matthias


People also ask

Can you read from a closed channel Golang?

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!") }

What happens when you read from a closed channel?

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 .

How does go select work?

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.

What happens when a channel is closed?

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.


2 Answers

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.

like image 168
Stephen Weinberg Avatar answered Sep 23 '22 04:09

Stephen Weinberg


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

like image 43
Sonia Avatar answered Sep 22 '22 04:09

Sonia