英文:
How to reason about Go channel blocking in Go Concurrency Patterns fan-in example?
问题
这是一个来自Rob Pike关于Go并发模式的演讲的示例。我理解fan-in模式的思想,并且我知道在main函数中打印的消息的顺序是不确定的:我们只是打印出10个已准备好的消息。
然而,我并不完全理解调用的顺序以及什么会阻塞什么。
只使用了无缓冲通道,根据文档,无缓冲通道会阻塞发送者。
boring
函数启动一个goroutine,将字符串发送到无缓冲通道c
,然后将其返回。如果我理解正确,这个内部的goroutine被启动,但不会阻塞boring
函数。它可以立即将通道返回给fanIn
函数。但是fanIn
函数几乎做了相同的事情:它从输入通道接收值,并将它们发送到自己的通道中,然后返回。
阻塞是如何发生的?在这种情况下,什么阻塞了什么?如果能给出一个示意图解释就更好了,因为老实说,尽管我有直观的理解,但我还是想理解其中的确切逻辑。
我直观的理解是,boring
函数中的每个发送操作都会阻塞,直到在fanIn
函数中接收到该值,然后该值立即被发送到另一个通道中,因此它会被阻塞,直到在main
函数中接收到该值。粗略地说,这三个函数由于使用通道而紧密地相互绑定在一起。
英文:
package main
import (
"fmt"
"math/rand"
"time"
)
func boring(msg string) <-chan string { // Returns receive-only channel of strings.
c := make(chan string)
go func() { // We launch the goroutine from inside the function.
for i := 0; ; i++ {
c <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
}
}()
return c // Return the channel to the caller.
}
func fanIn(input1, input2 <-chan string) <-chan string {
c := make(chan string)
go func() {
for {
c <- <-input1
}
}()
go func() {
for {
c <- <-input2
}
}()
return c
}
func main() {
c := fanIn(boring("Joe"), boring("Ann"))
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
fmt.Println("You're both boring; I'm leaving.")
}
This is an example from Rob Pike's talk on Go Concurrency Patterns. I understand the idea behind the fan-in pattern and I understand that the order of messages printed in main is non-deterministic: we just print 10 messages that turn out to be ready.
What I do not completely understand, however, is the order of calls and what blocks what.
Only unbuffered channels are used so, as per the documentation, an unbuffered channel blocks the sender.
The boring
function launches a goroutine that sends strings to the unbuffered channel c
, which is returned. If I understand correctly, this inner goroutine is launched but doesn't block boring
. It can immediately return the channel in main
to the fanIn
function. But fanIn
does almost the same thing: it receives the values from the input channel and sends them to its own channel that is returned.
How does the blocking happen? What blocks what in this case? A schematic explanation would be perfect because, honestly, even though I have an intuitive understanding, I would like to understand the exact logic behind it.
My intuitive understanding is that each send inside boring
blocks until the value is received in fanIn
, but then the value is immediately sent to another channel so it gets blocked until the value is received in main
. Roughly speaking, the three functions are tightly bound to each other due to the use of channels
答案1
得分: 3
阻塞是如何发生的?在这种情况下,什么会被阻塞?
在无缓冲通道上,每次发送操作都会被阻塞,直到另一侧有相应的接收操作(或者通道为nil
,这种情况下没有接收者)。在main
函数中,对boring
和fanIn
的调用是按顺序进行的。特别是这一行代码:
c := fanIn(boring("Joe"), boring("Ann"))
它的求值顺序是:
boring("Joe")
boring("Ann")
fanIn
boring("Joe")
和boring("Ann")
中的发送操作在fanIn
中有相应的接收操作,因此它们会被阻塞,直到fanIn
运行。因此,boring
函数会启动自己的goroutine,以确保在fanIn
开始接收之前返回通道。
fanIn
中的发送操作在main
中有相应的接收操作,因此它们会被阻塞,直到fmt.Println(<-c)
执行。因此,fanIn
会启动自己的goroutine(s),以确保在main
开始接收之前返回输出通道。
最后,main
的执行到达fmt.Println(<-c)
,并开始执行。接收c
会解除对c <- <-input[1|2]
的阻塞,接收<-input[1|2]
会解除对c <- fmt.Sprintf("%s %d", msg, i)
的阻塞。
如果在main
中移除接收操作,main
仍然可以继续执行,并且程序会立即退出,因此不会发生死锁。
英文:
> How does the blocking happen? What blocks what in this case?
Each send on an unbuffered channel blocks if there is no corresponding receive operation on the other side (or if the channel is nil
, which becomes a case of having no receiver).
Consider that in main
the calls to boring
and fanIn
happen sequentially. In particular this line:
c := fanIn(boring("Joe"), boring("Ann"))
has order of evaluation:
boring("Joe")
boring("Ann")
fanIn
The send operations in boring("Joe")
and boring("Ann")
have a corresponding receive operation in fanIn
, so they would block until fanIn
runs. Hence boring
spawns its own goroutine to ensure it returns the channel before fanIn
can start receiving on it.
The send operations in fanIn
have then a corresponding receive operation in main
, so they would block until fmt.Println(<-c)
runs. Hence fanIn
spawns its own goroutine(s) to ensure it returns the out channel before main
can start receiving on it.
Finally main
's execution gets to fmt.Println(<-c)
and sets everything in motion. Receiving on c
unblocks c <- <-input[1|2]
, and receiving on <-input[1|2]
unblocks c <- fmt.Sprintf("%s %d", msg, i)
.
If you remove the receive operation in main
, main
can still proceed execution and the program exits right away, so no deadlock occurs.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论