breaking out of a select statement when all channels are closed

前端 未结 5 1369
后悔当初
后悔当初 2021-01-30 00:48

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 ca

相关标签:
5条回答
  • 2021-01-30 01:13

    When I came across such a need I took the below approach:

    var wg sync.WaitGroup
    wg.Add(2)
    
    go func() {
      defer wg.Done()
      for p := range mins {
        fmt.Println("Min:", p) 
      }
    }()
    
    go func() {
      defer wg.Done()
      for p := range maxs {
        fmt.Println("Max:", p) 
      }
    }()
    
    wg.Wait()
    

    I know this is not with single for select loop, but in this case I feel this is more readable without 'if' condition.

    0 讨论(0)
  • 2021-01-30 01:16

    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

    0 讨论(0)
  • 2021-01-30 01:20

    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.

    0 讨论(0)
  • 2021-01-30 01:32

    Why not use goroutines? As your channels are getting closed, the whole thing turns into a simple range loop.

    func foo(c chan whatever, prefix s) {
            for v := range c {
                    fmt.Println(prefix, v)
            }
    }
    
    // ...
    
    go foo(mins, "Min:")
    go foo(maxs, "Max:")
    
    0 讨论(0)
  • 2021-01-30 01:39

    I wrote a package which provides a function to solve this problem (among several others):

    https://github.com/eapache/channels

    https://godoc.org/github.com/eapache/channels

    Check out the Multiplex function. It uses reflection to scale to an arbitrary number of input channels.

    0 讨论(0)
提交回复
热议问题