“消耗或放回”Go频道

时间:2015-12-31 23:13:30

标签: go channel

我正在尝试使用两个单独的消费者去例程,它们会从输入通道中过滤掉偶数和奇数。这只是一个玩具示例,以便查看是否可以让消费者对输入通道中读取的消息执行某些操作(如果匹配特定条件),否则将其放回输入通道。

我目前的代码如下:

package main

func filterOdd(ch chan int, out chan int) {
    val := <- ch
    if val % 2 == 0 {
        ch <- val
    } else {
        out <- val
    }
}
func filterEven(ch chan int, out chan int) {
    val := <- ch
    if val % 2 != 0 {
        ch <- val
    } else {
        out <- val
    }
}

func main() {
    even := make(chan int)
    odd := make(chan int)
    input := make(chan int)
    go filterOdd(input, odd)
    go filterEven(input, even)
    for i:=1; i <= 10; i++ {
        input <- i
    }

    println("Even...")
    for i := range even {
        println(i)
    }

    println("Odd...")
    for i := range odd {
        println(i)
    }
}

但是,这会产生以下输出:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /tmp/sandbox594577124/main.go:27 +0x140

goroutine 4 [chan send]:
main.filterOdd(0x10336100, 0x103360c0)
    /tmp/sandbox594577124/main.go:8 +0xc0
created by main.main
    /tmp/sandbox594577124/main.go:24 +0xc0

链接到Go Playground:https://play.golang.org/p/9RIvFsGKI-

1 个答案:

答案 0 :(得分:2)

你有一个死锁,因为你发送到out时你的偶数和奇数goroutine都被阻止了,因为没有任何东西正在读取它。为什么没有阅读out?因为main goroutine在发送到input时被阻止,因为没有任何内容从中读取。为什么没有阅读input?因为从中读取的两个goroutine被阻止了。

此外,filterEvenfilterOdd只会运行一次,除非您将其内容包装在for { }中(但之后他们永远不会停止,直到您break) 。另一方面,当range even没有留下任何内容时,range odd会阻止(even永不发生),因为频道上的range仅在频道停止时停止已关闭或break被调用。

一般而言,只要您知道何时可以关闭频道,这些都不是难以解决的问题。根据您的描述,这会变得更加困难。没有一个goroutine知道什么时候可以关闭input,因为所有三个都写入它并且两个也从它读取。您可以使用sync.WaitGroup确保在关闭之前已经处理了input频道中的所有内容。一旦关闭,其他两个goroutine就可以将其用作关闭自己频道的信号,并breakreturn完成投放。

但是,对inout通道的写入仍会阻塞,直到有相应的读取,因为它们是无缓冲的。但是,如果通过将大小指定为make的第二个参数来缓冲它们,则写入不会阻塞直到通道已满。既然您知道evenodd写入的内容比main发送给input的内容更多,您可以将其用作安全缓冲容量。

以下是使用带有缓冲频道的WaitGroup代码的示例:https://play.golang.org/p/VXqfwUwRcx

如果您不想要缓冲频道,您还可以使用另一对goroutine捕获值,并在片段完成后将其发送回main。这种方式写在evenodd频道不会阻止:https://play.golang.org/p/i5vLDcsK1v

否则,如果不需要同时打印每个频道的内容,您可以使用这两个额外的goroutine从频道中读取并立即打印:https://play.golang.org/p/OCaUTcJkKB