《Go语言并发编程》一书中描述的缓冲通道的不理解

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

Incomprehension of buffered channels described in the "Concurrency in Go" book

问题

我读了Katherine Cox-Buday写的《Go并发编程》这本书,但是我不理解关于带缓冲通道示例的注释。

作者说:

如果一个向通道写入数据的goroutine知道它将写入的次数,那么创建一个容量等于写入次数的带缓冲通道会很有用。

听起来很清楚,但是示例让我感到困惑。

  1. 第一个示例 - 源码:https://github.com/kat-co/concurrency-in-go-src/blob/master/gos-concurrency-building-blocks/channels/fig-using-buffered-chans.go#L13
    var stdoutBuff bytes.Buffer         // <1>
	defer stdoutBuff.WriteTo(os.Stdout) // <2>

	intStream := make(chan int, 4) // <3>
	go func() {
		defer close(intStream)
		defer fmt.Fprintln(&stdoutBuff, "Producer Done.")
		for i := 0; i < 5; i++ {
			fmt.Fprintf(&stdoutBuff, "Sending: %d\n", i)
			intStream <- i
		}
	}()

	for integer := range intStream {
		fmt.Fprintf(&stdoutBuff, "Received %v.\n", integer)
	}

带有注释<3>的那一行有以下解释:

在这里,我们创建了一个容量为1的带缓冲通道。

但是实际上是4,而不是1。这是一个错误吗?

  1. 第二个示例 - 通道所有权,源码:https://github.com/kat-co/concurrency-in-go-src/blob/master/gos-concurrency-building-blocks/channels/fig-chan-ownership.go
    chanOwner := func() <-chan int {
		resultStream := make(chan int, 5) // <1>
		go func() {                       // <2>
			defer close(resultStream) // <3>
			for i := 0; i <= 5; i++ {
				resultStream <- i
			}
		}()
		return resultStream // <4>
	}

标记为<1>的那一行有以下注释:

由于我们知道我们将产生六个结果,所以我们创建了一个容量为五的带缓冲通道,以便goroutine尽快完成。

我完全不理解这个注释。Goroutine将被阻塞,因为通道的容量是5条消息,而将会产生6条消息,所以它将等待接收者接收第一条消息。

英文:

I read the book "Concurrency in Go" written by Katherine Cox-Buday and I don't understand comments for examples of buffered channels.

The author says:

if a goroutine making writes to a channel has knowledge of how many writes it will make,
it can be useful to create a buffered channel whose capacity is the number of writes to be made

It sounds clear, but examples are confusing.

  1. First example - source: https://github.com/kat-co/concurrency-in-go-src/blob/master/gos-concurrency-building-blocks/channels/fig-using-buffered-chans.go#L13
    var stdoutBuff bytes.Buffer         // &lt;1&gt;
	defer stdoutBuff.WriteTo(os.Stdout) // &lt;2&gt;

	intStream := make(chan int, 4) // &lt;3&gt;
	go func() {
		defer close(intStream)
		defer fmt.Fprintln(&amp;stdoutBuff, &quot;Producer Done.&quot;)
		for i := 0; i &lt; 5; i++ {
			fmt.Fprintf(&amp;stdoutBuff, &quot;Sending: %d\n&quot;, i)
			intStream &lt;- i
		}
	}()

	for integer := range intStream {
		fmt.Fprintf(&amp;stdoutBuff, &quot;Received %v.\n&quot;, integer)
	}

The line with comment &lt;3&gt; has the following explanation:

Here we create a buffered channel with a capacity of one.

There is 4, not 1. Is it a mistake?

  1. Second example - channel ownership, source: https://github.com/kat-co/concurrency-in-go-src/blob/master/gos-concurrency-building-blocks/channels/fig-chan-ownership.go
    chanOwner := func() &lt;-chan int {
		resultStream := make(chan int, 5) // &lt;1&gt;
		go func() {                       // &lt;2&gt;
			defer close(resultStream) // &lt;3&gt;
			for i := 0; i &lt;= 5; i++ {
				resultStream &lt;- i
			}
		}()
		return resultStream // &lt;4&gt;
	}

The line marked as &lt;1&gt;, has the following comment:

Since we know we will produce six results, we create a buffered channel of five
so that the goroutine can complete as quickly as possible.

I completely don't understand this comment. Goroutine will be blocked, because the channel has the capacity of 5 messages and there will be produced 6 messages, so it will wait until a receiver takes the first message.

答案1

得分: 2

由于我们知道我们将产生六个结果,所以我们创建了一个缓冲容量为五的通道,以便goroutine能够尽快完成。

你是正确的,goroutine将会在接收到一个值之前阻塞。

创建一个容量比要发送的值少一个的通道是没有意义的。如果通道的容量等于或大于要发送的值的数量,那么可以消除goroutine:

chanOwner := func() <-chan int {
    resultStream := make(chan int, 6)
    for i := 0; i < cap(resultStream); i++ {
        resultStream <- i
    }
    close(resultStream)
    return resultStream
}()

或者通过消除匿名函数来实现:

chanOwner := make(chan int, 6)
for i := 0; i < cap(chanOwner); i++ {
    resultStream <- i
}
close(chanOwner)
英文:

> Since we know we will produce six results, we create a buffered channel of five so that the goroutine can complete as quickly as possible.

You are correct that the goroutine will block until a value is received.

It doesn't make sense to create a channel with capacity one less than the number of values to be sent. The goroutine can be eliminated if the channel capacity is equal to or greater than the number of values:

chanOwner := func() &lt;-chan int {
    resultStream := make(chan int, 6)
    for i := 0; i &lt; cap(resultStream); i++ {
        resultStream &lt;- i
    }
    close(resultStream)
    return resultStream
}()

or this by eliminating the anonymous function:

    chanOwner := make(chan int, 6)
    for i := 0; i &lt; cap(chanOwner); i++ {
        resultStream &lt;- i
    }
    close(chanOwner)

答案2

得分: 1

是的,听起来这本书需要一个更好的编辑!


通道容量确实作为make的第二个参数指定:

intStream := make(chan int, 4) // 容量为4的缓冲通道(不是1!)

如果在通道上没有进行读取操作,那么写入的 goroutine 将会在不阻塞的情况下向缓冲通道(容量为5)写入5次。第6次写入将会阻塞,直到缓冲区拥堵减少。

如果其他的 goroutine 从通道中读取数据,即使只有一次,缓冲区就会释放出空间,写入的 goroutine 就能够完成最后一次写入。

英文:

Yes, it sounds like this book needs a better editor!


1)

the channel capacity is indeed indicated as the 2nd argument to make:

intStream := make(chan int, 4) // buffered-channel of capacity 4 (not 1!)

2)

If no reads are done on the channel - then yes the writing goroutine will write 5 times to the buffered channel (of capacity 5) without issue (i.e. without blocking). The 6th write will indeed block - and the goroutine will not return until the buffer congestion reduces.

If some other goroutine does read from the channel - even just once - then the buffer frees up and the writer goroutine will be able to complete the final write.

答案3

得分: 1

有4个,而不是1个。这是一个错误吗?

这似乎是一个打字错误。正如文档中明确说明的那样,make 函数的第二个参数是通道的容量:

通道:通道的缓冲区使用指定的缓冲区容量进行初始化。如果为零,或者省略了大小,通道将是无缓冲的。

因此,make(chan int, 4) 是一个容量为4的通道。

协程将被阻塞,因为通道的容量为5个消息,而将会产生6个消息,所以它将等待接收者接收第一个消息。

正确,声明的通道容量为5,如果没有接收者,第六个也是最后一个发送操作将会被阻塞,因为通道缓冲区已满。

在所有方面都保持善意的前提下,这本书可能漏掉了一轮校对。

英文:

> There is 4, not 1. Is it a mistake?

It seems a typo. As clearly stated in the documentation, the second argument to make is the channel capacity:

> Channel: The channel's buffer is initialized with the specified
buffer capacity. If zero, or the size is omitted, the channel is unbuffered.

Therefore make(chan int, 4) is a chan with capacity 4.

> Goroutine will be blocked, because the channel has the capacity of 5 messages and there will be produced 6 messages, so it will wait until a receiver takes the first message.

Correct, the declared chan has capacity 5, and if there's no receiver, the sixth and last send operation will indeed block, as the channel buffer is full.

Assuming good faith on all sides, the book probably missed a round of proofreading.

huangapple
  • 本文由 发表于 2021年6月13日 01:51:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/67951539.html
匿名

发表评论

匿名网友

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

确定