“消费或放回” Go 通道

huangapple go评论85阅读模式
英文:

"Consume or put back" Go channels

问题

我正在尝试拥有两个独立的消费者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-

英文:

I am trying to have two separate consumer go routines, that would filter out even and odd numbers from the input channel. This is just a toy example in order to see if it is possible to have consumers do something with the message read from the input channel if it matches certain condition, otherwise put back onto the input channel.

My current code is as follows:

package main

func filterOdd(ch chan int, out chan int) {
	val := &lt;- ch
	if val % 2 == 0 {
		ch &lt;- val
	} else {
		out &lt;- val
	}
}
func filterEven(ch chan int, out chan int) {
	val := &lt;- ch
	if val % 2 != 0 {
		ch &lt;- val
	} else {
		out &lt;- 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 &lt;= 10; i++ {
		input &lt;- i
	}
	
	println(&quot;Even...&quot;)
	for i := range even {
		println(i)
	}
	
	println(&quot;Odd...&quot;)
	for i := range odd {
		println(i)
	}
}

However, this produces the following output:

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

Link to the Go Playground: https://play.golang.org/p/9RIvFsGKI-

答案1

得分: 2

你的死锁问题是因为你的偶数和奇数协程在发送到out时被阻塞,因为没有任何东西从中读取。为什么没有任何东西从out中读取?因为main协程在发送到input时被阻塞,因为没有任何东西从中读取。为什么没有任何东西从input中读取?因为两个从中读取的协程都被阻塞了。

此外,filterEvenfilterOdd只会运行一次,除非你将它们的内容包装在for { }中(但这样它们将永远不会停止,直到你使用break)。另一方面,当没有剩余的内容写入even时,range even会被阻塞(range odd永远不会发生),因为通道上的range只有在通道关闭或调用break时才会停止。

一般来说,只要你知道何时可以关闭通道,这些问题都不难解决。根据你的描述,这变得更加困难。没有一个协程知道何时可以关闭input,因为所有三个协程都向其写入,并且其中两个还从中读取。你可以使用sync.WaitGroup来确保在关闭通道之前已经处理了放入input通道的所有内容。一旦关闭了通道,其他两个协程可以将其作为关闭自己的通道的信号,并使用breakreturn来完成运行。

然而,对inout通道的写入仍然会阻塞,直到有相应的读取,因为它们是无缓冲的。但是,如果通过在make的第二个参数中指定大小来缓冲它们,写入操作将不会阻塞,直到通道已满。由于你知道evenodd中写入的内容不会超过main发送到input的内容,你可以将其作为安全缓冲容量。

这是使用WaitGroup和带缓冲通道的代码示例:https://play.golang.org/p/VXqfwUwRcx

如果你不想使用缓冲通道,你还可以使用另一对协程来捕获值,并在完成后将它们作为切片发送回main。这样,对evenodd通道的写入操作就不会被阻塞:https://play.golang.org/p/i5vLDcsK1v

否则,如果你不需要一次打印每个通道的内容,你可以使用这两个额外的协程从通道中读取并立即打印:https://play.golang.org/p/OCaUTcJkKB

英文:

You have a deadlock because your even and odd goroutines are blocked on sending to out because nothing is reading from it. Why is nothing reading out? Because the main goroutine is blocked on sending to input because nothing is reading from it. Why is nothing reading from input? Because the two goroutines that would read from it are blocked.

Also, both filterEven and filterOdd will only run once unless you wrap their contents in for { } (but then they'll never stop until you break). On the other hand, range even will block (and range odd never happens) when nothing is left to write to even , because range over a channel only stops when the channel is closed or break is called.

In general, these aren't hard problems to solve as long as you know when you can close a channel. With what you're describing, that gets more difficult. None of the goroutines know when it's OK to close input, because all three write to it and two also read from it. You can use a sync.WaitGroup to make sure that everything you've put into the input channel has been processed before closing it. Once it's closed, the two other goroutines can use that as a signal to close their own channels and break or return to finish running.

However, writes to the in and out channels will still block until there is a corresponding read, because they are unbuffered. However, if you buffer them by specifying a size as the second argument to make, writes won't block until the channel is full. Since you know neither even or odd will have more written to them than what main send to input, you can use that as a safe buffer capacity.

Here's an example of using a WaitGroup with buffered channels for your code: https://play.golang.org/p/VXqfwUwRcx

If you don't want buffered channels, you can also use another pair of goroutines to capture the values and send them back to main as slices once finished. This way writes on the even and odd channels don't block: https://play.golang.org/p/i5vLDcsK1v

Otherwise, if don't need to print the contents of each channel all at once, you can use those two extra goroutines to read from the channels and print right away: https://play.golang.org/p/OCaUTcJkKB

huangapple
  • 本文由 发表于 2016年1月1日 07:13:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/34552334.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定