我有两个goroutines独立生成数据,每个数据都发送到一个频道。在我的主要goroutine中,我想在它们进入时消耗每个输出,但不关心它们进入的顺序。每个通道在耗尽其输出时将自行关闭。虽然select语句是像这样独立地使用输入的最好的语法,但我还没有看到一个简洁的方法来循环遍历每个通道,直到两个通道都关闭。
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
}
}
我能想到的最好的是以下内容(只是草拟,可能有编译错误):
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}
}
但是,如果您使用超过两个或三个频道,这看起来会变得难以忍受。我所知道的唯一另一种方法是在switch语句中使用一个timout案例,该案例要么小到足以提前退出,要么在最终循环中注入太多停机时间。有没有更好的方法来测试select语句中的通道?
答案 0 :(得分:84)
您的示例解决方案效果不佳。一旦其中一个关闭,它将始终可用于立即通信。这意味着你的goroutine永远不会屈服,其他渠道可能永远不会准备好。你会有效地进入无限循环。我发布了一个示例来说明效果:http://play.golang.org/p/rOjdvnji49
那么,我该如何解决这个问题呢?零通道从未准备好进行通信。所以每次你遇到一个封闭的通道,你都可以忽略那个通道,确保它再也不会被选中。这里的可运行示例: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
}
}
至于害怕变得笨拙,我认为不会。您很少有频道同时前往太多地方。这很少会出现,我的第一个建议只是处理它。将10个通道与nil进行比较的长if语句并不是尝试处理选择中10个通道的最差部分。
答案 1 :(得分:21)
在某些情况下关闭很好,但不是全部。我不会在这里使用它。相反,我只会使用完成频道:
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--
}
}
在操场上完成工作示例:http://play.golang.org/p/Cqd3lg435y
答案 2 :(得分:9)
为什么不使用goroutines?随着您的频道关闭,整个事情变成了一个简单的范围循环。
func foo(c chan whatever, prefix s) {
for v := range c {
fmt.Println(prefix, v)
}
}
// ...
go foo(mins, "Min:")
go foo(maxs, "Max:")
答案 3 :(得分:1)
我写了一个包,它提供了解决这个问题的功能(以及其他几个):
https://github.com/eapache/channels
https://godoc.org/github.com/eapache/channels
查看Multiplex
功能。它使用反射缩放到任意数量的输入通道。
答案 4 :(得分:0)
当我遇到这种需要时,我采取了以下方法:
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()
我知道这不是单选择循环,但是在这种情况下,我觉得如果没有'if'条件,它更易读。