当所有通道都关闭时,会中断select语句

时间:2012-12-02 03:55:00

标签: go

我有两个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语句中的通道?

5 个答案:

答案 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'条件,它更易读。