如何理解Go并发模式中的fan-in示例中的Go通道阻塞问题?

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

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函数中,对boringfanIn的调用是按顺序进行的。特别是这一行代码:

c := fanIn(boring("Joe"), boring("Ann"))

它的求值顺序是:

  1. boring("Joe")
  2. boring("Ann")
  3. 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(&quot;Joe&quot;), boring(&quot;Ann&quot;))

has order of evaluation:

  1. boring(&quot;Joe&quot;)
  2. boring(&quot;Ann&quot;)
  3. fanIn

The send operations in boring(&quot;Joe&quot;) and boring(&quot;Ann&quot;) 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(&lt;-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(&lt;-c) and sets everything in motion. Receiving on c unblocks c &lt;- &lt;-input[1|2], and receiving on &lt;-input[1|2] unblocks c &lt;- fmt.Sprintf(&quot;%s %d&quot;, 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.

huangapple
  • 本文由 发表于 2022年1月24日 16:00:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/70830632.html
匿名

发表评论

匿名网友

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

确定