在Golang中同时从多个通道读取

20

我是Go语言的新手。现在我正在尝试弄清楚如何在Go中创建一个任意到一的通道,设置如下:

假设我有两个并发执行的goroutine numgen1和numgen2,并将数字写入通道num1和num2。 我想在一个新进程addnum中添加从numgen1和numgen2发送的数字。我尝试了类似这样的代码:

func addnum(num1, num2, sum chan int) {
    done := make(chan bool)
    go func() {
        n1 := <- num1
        done <- true
    }()
        n2 := <- num2
        <- done
    sum <- n1 + n2
}

但这似乎是错误的。请问有人可以给我一些想法吗?

非常感谢您的帮助。

3个回答

19

根据您的需求,每次迭代时您可能需要同时读取两个通道(即一种类似于“zip”函数的方法)。您可以使用选择器来完成这个操作,就像用户860302的回答一样:

func main() {

  c1 := make(chan int)
  c2 := make(chan int)
  out := make(chan int)

  go func(in1, in2 <-chan int, out chan<- int) {
    for {
      sum := 0
      select {
      case sum = <-in1:
        sum += <-in2

      case sum = <-in2:
        sum += <-in1
      }
      out <- sum
    }
  }(c1, c2, out)
}

这将永远运行。我喜欢终止像这样的goroutine的首选方法是关闭输入通道。在这种情况下,您需要等待两个通道都关闭,然后在终止之前close(out)

提示:请注意,在goroutine形式参数中使用方向通道的用法。以这种方式编写时,编译器会捕获更多的错误。快乐!


请注意,您在内部函数中使用了c1和c2而不是in1和in2。 - zk82
嗨,很好,特别是你的内/外注释,但请注意,在内部函数中你使用了c1和c2而不是in1和in2。另外,你在select中隐藏了sum,所以当你将其发送到out时它为0。抱歉重复发布。 - zk82
谢谢你的纠正 - 我已经按照你的建议更新了代码。 - Rick-777
请将此建议与@publysher的建议进行比较,后者是实现类似功能的更简单的方法。在其他goroutine可能会被阻塞更长时间并且不会增加死锁风险的情况下,他的建议是可以接受的。如果有疑问,我上面列出的策略是更安全的选择。 - Rick-777

8
最简单的答案是:
func addnum(num1, num2, sum chan int) {
  n1 := <- num1
  n2 := <- num2
  sum <- n1 + n2
}

由于需要同时使用num1num2进行计算,否则这样做毫无意义。毕竟,有两种可能的执行顺序:
  1. num1生成一个数字,接着num2
  2. num2生成一个数字,接着num1
在第一种情况下,我们的通道读取与执行顺序完全对应。 在第二种情况下,我们的第一次读取将阻塞,直到num1最终产生数字;由于num2通道已经有了数字,第二次读取将几乎立即完成。
如果您想了解Go语言中的更多有关通道的信息,建议查看http://godoc.org/github.com/thomas11/csp--这是一个用Go编写的Hoare的CSP示例集合。

谢谢 :) 如果我想让通道持续读取,我可以分别为num1和num2编写两个for循环吗? - Jing
1
不行,因为这会先完全消耗 range num1(可能永远不会)然后才会从 range num2 读取第一个条目。你可以将整个函数体包装在一个 for { ... } 循环中以保持计算。 - publysher
这似乎比被接受的答案更容易,而且它是“足够正确”的。 - Vlad the Impala

4
为了回答“同时从多个通道读取”的问题,
有一种方法可以同时监听多个通道:
func main() {

    c1 := make(chan string)
    c2 := make(chan string)

    ...
    go func() {
        for {
            select {
                case msg1 := <- c1:
                fmt.Println(msg1)

                case msg2 := <- c2:
                fmt.Println(msg2)
             }
        }
    }()

在这个例子中,我创建了一个名为msg1和msg2的通道。 然后我创建了一个带有无限循环的go程。在这个循环中,我同时监听msg1和msg2。 这个系统允许您同时读取多个通道并在它们到达时处理消息。
为了避免泄漏,我应该添加另一个通道来停止goroutine。

1
这不是同时读取,而是同时检查并在通道准备好时读取。因此,在现实世界中,您需要同步代码。 - Javier Neyra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接