当所有通道都关闭时,跳出select语句。

huangapple go评论91阅读模式
英文:

breaking out of a select statement when all channels are closed

问题

我有两个独立产生数据的goroutine,每个都将数据发送到一个通道中。在我的主goroutine中,我想要按照它们到达的顺序消费每个输出,但是不关心它们到达的顺序。每个通道在耗尽其输出后会自动关闭。虽然使用select语句是处理这种独立输入的最好语法,但我还没有找到一种简洁的方法来循环处理这两个通道直到它们都关闭。

for {
    select {
    case p, ok := <-mins:
        if ok {
            fmt.Println("Min:", p) //消费输出
        }
    case p, ok := <-maxs:
        if ok {
            fmt.Println("Max:", p) //消费输出
        }
    //default: //无法保证在通道打开时不会发生这种情况
    //    break //理想情况下,我只会在两个通道都完成时离开无限循环
    }
}

我能想到的最好的方法是以下方式(只是草图,可能会有编译错误):

for {
    minDone, maxDone := false, false
    select {
    case p, ok := <-mins:
        if ok {
            fmt.Println("Min:", p) //消费输出
        } else {
            minDone = true
        }
    case p, ok := <-maxs:
        if ok {
            fmt.Println("Max:", p) //消费输出
        } else {
            maxDone = true
        }
    }
    if (minDone && maxDone) {break}
}

但是,如果你要处理超过两个或三个通道,这种方法看起来会变得不可行。我所知道的另一种方法是在switch语句中使用超时case,但这可能会导致过早退出,或者在最终循环中引入太多的停机时间。有没有更好的方法来测试通道是否在select语句中?

英文:

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 := &lt;-mins:
        if ok {
            fmt.Println(&quot;Min:&quot;, p) //consume output
        }
    case p, ok := &lt;-maxs:
        if ok {
            fmt.Println(&quot;Max:&quot;, p) //consume output
        }
    //default: //can&#39;t guarantee this won&#39;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 := &lt;-mins:
        if ok {
            fmt.Println(&quot;Min:&quot;, p) //consume output
        } else {
            minDone = true
        }
    case p, ok := &lt;-maxs:
        if ok {
            fmt.Println(&quot;Max:&quot;, p) //consume output
        } else {
            maxDone = true
        }
    }
    if (minDone &amp;&amp; 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?

答案1

得分: 157

你的示例解决方案不会很好地工作。一旦其中一个通道关闭,它将立即可用于通信。这意味着你的goroutine永远不会让出,并且其他通道可能永远不会准备好。你将进入一个无限循环。我在这里发布了一个示例来说明这种效果:http://play.golang.org/p/rOjdvnji49

那么,我该如何解决这个问题呢?一个nil通道永远不会准备好通信。所以每次遇到一个关闭的通道时,你可以将该通道置为nil,确保它不会再被选择。这里有一个可运行的示例:http://play.golang.org/p/8lkV_Hffyj

for {
	select {
	case x, ok := &lt;-ch:
		fmt.Println(&quot;ch1&quot;, x, ok)
		if !ok {
			ch = nil
		}
	case x, ok := &lt;-ch2:
		fmt.Println(&quot;ch2&quot;, x, ok)
		if !ok {
			ch2 = nil
		}
	}

	if ch == nil &amp;&amp; ch2 == nil {
		break
	}
}

至于担心它变得难以管理,我认为不会。很少有情况下你的通道会同时去很多地方。这种情况出现得如此罕见,以至于我第一个建议就是处理它。将10个通道与nil进行长时间的if语句比较并不是处理10个通道中最糟糕的部分。

英文:

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 := &lt;-ch:
		fmt.Println(&quot;ch1&quot;, x, ok)
		if !ok {
			ch = nil
		}
	case x, ok := &lt;-ch2:
		fmt.Println(&quot;ch2&quot;, x, ok)
		if !ok {
			ch2 = nil
		}
	}

	if ch == nil &amp;&amp; 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.

答案2

得分: 33

在某些情况下,关闭通道是很好的,但并非所有情况都适用。我不会在这里使用它。相反,我只会使用一个完成通道:

for n := 2; n > 0; {
	select {
	case p := <-mins:
		fmt.Println("最小值:", p)	//消费输出
	case p := <-maxs:
		fmt.Println("最大值:", p)	//消费输出
	case <-done:
		n--
	}
}

完整的工作示例请参见playground:http://play.golang.org/p/Cqd3lg435y

英文:

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 &gt; 0; {
	select {
	case p := &lt;-mins:
		fmt.Println(&quot;Min:&quot;, p)	//consume output
	case p := &lt;-maxs:
		fmt.Println(&quot;Max:&quot;, p)	//consume output
	case &lt;-done:
		n--
	}
}

Complete working example at the playground: http://play.golang.org/p/Cqd3lg435y

答案3

得分: 11

为什么不使用goroutines?当你的通道被关闭时,整个过程就变成了一个简单的范围循环。

func foo(c chan whatever, prefix s) {
        for v := range c {
                fmt.Println(prefix, v)
        }
}

// ...

go foo(mins, "Min:")
go foo(maxs, "Max:")
英文:

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, &quot;Min:&quot;)
go foo(maxs, &quot;Max:&quot;)

答案4

得分: 3

当我遇到这样的需求时,我采用了以下方法:

var wg sync.WaitGroup
wg.Add(2)

go func() {
  defer wg.Done()
  for p := range mins {
    fmt.Println("最小值:", p) 
  }
}()

go func() {
  defer wg.Done()
  for p := range maxs {
	fmt.Println("最大值:", p) 
  }
}()

wg.Wait()

我知道这不是使用单个for select循环,但在这种情况下,我觉得这种写法更易读,而无需使用"if"条件。

英文:

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(&quot;Min:&quot;, p) 
  }
}()

go func() {
  defer wg.Done()
  for p := range maxs {
	fmt.Println(&quot;Max:&quot;, 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.

答案5

得分: 2

我写了一个包,其中提供了一个函数来解决这个问题(以及其他几个问题):

https://github.com/eapache/channels

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

查看Multiplex函数。它使用反射来扩展到任意数量的输入通道。

英文:

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.

huangapple
  • 本文由 发表于 2012年12月2日 11:54:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/13666253.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定