【问题标题】:"Consume or put back" Go channels [duplicate]“消费或放回”Go频道[重复]
【发布时间】:2016-04-05 18:35:11
【问题描述】:

我正在尝试创建两个单独的消费者 go 例程,这将过滤掉输入通道中的偶数和奇数。这只是一个玩具示例,目的是看看是否有可能让消费者对从输入通道读取的消息执行某些操作,如果它符合特定条件,则将其放回输入通道。

我目前的代码如下:

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-

【问题讨论】:

  • @ZanLynx:虽然这个问题说他们在示例代码中遇到了死锁,但听起来他们并没有尝试做与其他问题相同的事情。
  • @JamesHenstridge 当然不是一回事。但看起来这个问题的一般问题和解决方案是相同的。
  • 而且我刚刚注意到,即使你修复了缓冲,你的 for 循环也不在 goroutines 中,因此会阻塞,除非你总是让缓冲区大于循环大小。

标签: go channel


【解决方案1】:

您遇到了死锁,因为您的偶数和奇数 goroutine 在发送到 out 时被阻塞,因为没有任何内容从中读取。为什么没有阅读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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-28
    • 1970-01-01
    相关资源
    最近更新 更多