为什么Go Channel的缓冲区不能正确限制写入/读取?

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

Why does Go Channel's buffer not limiting writing/reading correctly?

问题

我正在尝试使用通道在两个Go协程之间进行通信。首先,我创建了一个整数通道,然后将其作为参数传递给一个打印从0到10的数字序列的Go协程。这些程序的输出没有意义。

这是主要的代码:

func Worker(identifier string, ch chan<- int) {
	fmt.Printf("进入工作器 %s\n", identifier)
	for i := 0; i < 10; i++ {
		fmt.Printf("写入 %d\n", i)
		ch <- i
	}

	fmt.Printf("退出工作器 %s\n", identifier)

	close(ch)
}

func main() {
	ch := make(chan int)
	go Worker("1", ch)

	for v := range ch {
		fmt.Printf("读取 %d\n", v)
	}
}

对于该代码的执行,我得到了以下输出:

进入工作器 1
写入 0
写入 1
读取 0
读取 1
写入 2
写入 3
读取 2
读取 3
写入 4
写入 5
读取 4
读取 5
写入 6
写入 7
读取 6
读取 7
写入 8
写入 9
读取 8
读取 9
退出工作器 1

请注意,有两个写入执行,然后是两个读取执行。

后来,我设置了一个缓冲区大小,代码如下:

func main() {
	ch := make(chan int, 3) // <= 缓冲区
	go Worker("1", ch)

	for v := range ch {
		fmt.Printf("读取 %d\n", v)
	}

}

然后,得到了以下输出:

进入工作器 1
写入 0
写入 1
写入 2
写入 3
写入 4
读取 0
读取 1
读取 2
读取 3
读取 4
写入 5
写入 6
写入 7
写入 8
写入 9
读取 5
读取 6
读取 7
读取 8
读取 9
退出工作器 1

请注意,现在有5个写入执行,然后是5个读取执行。

现在我们有了代码和输出,下面是最后一个问题:为什么这些执行会出现这种行为?在第一个执行中,不是应该每次只读取和写入一个数字吗?此外,为什么第二个执行每次读取和写入5个数字而不是3个(因为这是缓冲区大小)?

英文:

I am trying to use channels to communicate between two go routines. At first I created the channel of integers, then I passed it as parameter to a go routine which prints a sequence of numbers from 0 to 10. The outputs of those programs are not making sense.

This is the main code:

func Worker(identifier string, ch chan&lt;- int) {
	fmt.Printf(&quot;Entering worker %s\n&quot;, identifier)
	for i := 0; i &lt; 10; i++ {
		fmt.Printf(&quot;Writing %d\n&quot;, i)
		ch &lt;- i
	}

	fmt.Printf(&quot;Exiting worker %s\n&quot;, identifier)

	close(ch)
}

func main() {
	ch := make(chan int)
	go Worker(&quot;1&quot;, ch)

	for v := range ch {
		fmt.Printf(&quot;Reading %d\n&quot;, v)
	}
}

For that code execution I got this output:

Entering worker 1
Writing 0
Writing 1
Reading 0
Reading 1
Writing 2
Writing 3
Reading 2
Reading 3
Writing 4
Writing 5
Reading 4
Reading 5
Writing 6
Writing 7
Reading 6
Reading 7
Writing 8
Writing 9
Reading 8
Reading 9
Exiting worker 1

Please, note that there are two writing executions then two reading executions.

Late on, I set a buffer size to make function like this:

func main() {
	ch := make(chan int, 3) // &lt;= Buffer
	go Worker(&quot;1&quot;, ch)

	for v := range ch {
		fmt.Printf(&quot;Reading %d\n&quot;, v)
	}

}

And then, got this output:

Entering worker 1
Writing 0
Writing 1
Writing 2
Writing 3
Writing 4
Reading 0
Reading 1
Reading 2
Reading 3
Reading 4
Writing 5
Writing 6
Writing 7
Writing 8
Writing 9
Reading 5
Reading 6
Reading 7
Reading 8
Reading 9
Exiting worker 1

Please, note that now we have 5 writing execution then 5 reading execution.

Once we have the code and outputs, there goes the final question: Why did those executions behave like this? In the first, wasn't it supposed to read and write only one number per time? Beyond that, why the second execution reads and write 5 numbers per time instead of 3 (since this is the buffer size)?

答案1

得分: 3

你混淆了消息打印的时间和从通道读取或写入数字的时间。

"写入"消息并不是在写入发生时发生的。它们发生在写入之间的某个时间点。类似地,"读取"消息发生在读取之间的某个时间点。

以下是你的第一个片段可能被调度的一种方式,这将产生显示的输出:

  • 主线程尝试读取,并阻塞。
  • 工作线程打印"Writing 0"。
  • 工作线程写入0,主线程读取。
  • 工作线程打印"Writing 1"。
  • 工作线程尝试写入1,并阻塞。
  • 主线程打印"Reading 0"。
  • 主线程读取1。
  • 主线程打印"Reading 1"。
  • 主线程尝试读取,并阻塞。

控制权在主线程和工作线程之间以这种方式传递,每个线程在阻塞之前打印2条消息。

类似地,你的第二个片段可能按照以下方式被调度:

  • 主线程尝试读取,并阻塞。
  • 工作线程打印"Writing 0",并直接发送0给主线程。
  • 工作线程打印"Writing 1",并缓冲1。
  • 工作线程打印"Writing 2",并缓冲2。
  • 工作线程打印"Writing 3",并缓冲3。
  • 工作线程打印"Writing 4",并尝试发送4时阻塞。
  • 主线程完成了之前被阻塞的读取,并打印"Reading 0"。
  • 主线程读取缓冲的1,并打印"Reading 1"。
  • 主线程读取缓冲的2,并打印"Reading 2"。
  • 主线程读取缓冲的3,并打印"Reading 3"。
  • 主线程读取工作线程被阻塞的4,并打印"Reading 4"。
  • 主线程尝试读取,并阻塞。
  • 执行返回到工作线程...
英文:

You're mixing up when the messages are printed and when the numbers are read from or written to the channel.

"Writing" messages don't happen when a write happens. They happen at some point between writes. Similarly, "Reading" messages happen at some point between reads.

Here's one way your first snippet could have gotten scheduled, that would have produced the displayed output:

  • main attempts to read, and blocks.
  • Worker prints "Writing 0".
  • Worker writes 0, which main reads.
  • Worker prints "Writing 1".
  • Worker attempts to write 1, and blocks.
  • main prints "Reading 0".
  • main reads 1.
  • main prints "Reading 1".
  • main attempts to read, and blocks.

Control keeps passing between main and Worker like this, each printing 2 messages before blocking.

Similarly, your second snippet could have been scheduled like this:

  • main attempts to read, and blocks.
  • Worker prints "Writing 0", and sends a 0 straight to main.
  • Worker prints "Writing 1", and buffers a 1.
  • Worker prints "Writing 2", and buffers a 2.
  • Worker prints "Writing 3", and buffers a 3.
  • Worker prints "Writing 4", and blocks trying to send a 4.
  • main finishes the read it was blocked on, and prints "Reading 0".
  • main reads the buffered 1, and prints "Reading 1".
  • main reads the buffered 2, and prints "Reading 2".
  • main reads the buffered 3, and prints "Reading 3".
  • main reads the 4 that Worker was blocked on, and prints "Reading 4".
  • main attempts to read, and blocks.
  • Execution returns to Worker...

huangapple
  • 本文由 发表于 2023年3月19日 21:51:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75782452.html
匿名

发表评论

匿名网友

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

确定