英文:
"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 := <- 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)
}
}
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
中读取?因为两个从中读取的协程都被阻塞了。
此外,filterEven
和filterOdd
只会运行一次,除非你将它们的内容包装在for { }
中(但这样它们将永远不会停止,直到你使用break
)。另一方面,当没有剩余的内容写入even
时,range even
会被阻塞(range odd
永远不会发生),因为通道上的range
只有在通道关闭或调用break
时才会停止。
一般来说,只要你知道何时可以关闭通道,这些问题都不难解决。根据你的描述,这变得更加困难。没有一个协程知道何时可以关闭input
,因为所有三个协程都向其写入,并且其中两个还从中读取。你可以使用sync.WaitGroup
来确保在关闭通道之前已经处理了放入input
通道的所有内容。一旦关闭了通道,其他两个协程可以将其作为关闭自己的通道的信号,并使用break
或return
来完成运行。
然而,对in
和out
通道的写入仍然会阻塞,直到有相应的读取,因为它们是无缓冲的。但是,如果通过在make
的第二个参数中指定大小来缓冲它们,写入操作将不会阻塞,直到通道已满。由于你知道even
和odd
中写入的内容不会超过main
发送到input
的内容,你可以将其作为安全缓冲容量。
这是使用WaitGroup
和带缓冲通道的代码示例:https://play.golang.org/p/VXqfwUwRcx
如果你不想使用缓冲通道,你还可以使用另一对协程来捕获值,并在完成后将它们作为切片发送回main
。这样,对even
和odd
通道的写入操作就不会被阻塞: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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论