缓冲通道在Go中与我的预期有何不同?

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

How does the behavior of a buffered channel differ from my expectations in Go?

问题

我正在尝试理解缓冲通道的工作原理,并为此编写了一段代码。

根据我的预期,主goroutine开始并继续执行,直到c<-4,此时被阻塞,控制权转移到squares goroutine(因为缓冲区容量为3)。squares goroutine中的循环继续执行,直到第4次迭代,此时通道为空。对空通道的读取操作会被阻塞,因此控制权返回给主goroutine。此时对通道的写入操作(c<-4)被执行,我们打印"main() stopped",程序结束。

也就是说,我期望的输出应该是:

main() started
1
4
9
main() stopped

但实际输出是:

main() started
1
4
9
16
main() stopped

为什么会这样?我是否对通道的工作原理有所遗漏?

英文:

I am trying to understand how a buffered channel works and wrote a code snippet for it

package main

import (
	&quot;fmt&quot;
)

func squares(c chan int) {
	for i := 0; i &lt; 4; i++ {
		num := &lt;-c
		fmt.Println(num * num)
	}
}

func main() {
	fmt.Println(&quot;main() started&quot;)
	c := make(chan int, 3)
	
        go squares(c)

	c &lt;- 1
	c &lt;- 2
	c &lt;- 3
	c &lt;- 4 // blocks here

	fmt.Println(&quot;main() stopped&quot;)
}

As per how I expected the program to behave, the main goroutine starts and continues till c<-4, which at that point gets blocked and the control goes to square goroutine (as the buffer capacity is 3). The loop in the squares goroutine continues till 4th iteration at which point the channel is empty. A read operation on an empty channel is blocking and so the control goes back to main goroutine. At that point the write operation to the channel (c<-4) gets executed, we print "main() stopped" and the program ends.

Meaning I expected the output to be,

main() started
1
4
9
main() stopped

But I get an output,

main() started
1
4
9
16
main() stopped

How? Am I missing something about how exactly channels work?

答案1

得分: 4

这不是通道的工作方式。

Goroutine 是并发运行的。这意味着当一个 goroutine 发送数据到一个有缓冲的通道时,另一个等待从该通道接收数据的 goroutine 可以立即接收到它,而不需要等待通道填满。

至于程序的结束,当你将最后一个数字发送到通道时,并不能保证 goroutine 会在程序结束之前接收到它并打印输出,因为你没有等待 goroutine 完成。所以,运气好的话,它会运行并打印输出。但也会有其他情况下,程序在 goroutine 打印输出之前终止。

英文:

That is not how channels work.

Goroutines run concurrently. That means when a goroutine sends to a buffered channel, another goroutine waiting to receive from that channel can receive it immediately. It does not wait for the channel to fill up.

As for the ending of the program, when you send the last number to the channel, there is no guarantee that the goroutine will pick it up and print the output before the program ends, because you are not waiting for the goroutine to complete. So, by luck, it runs and prints the output. There will be other executions where this does not happen, and the program terminates before the goroutine can print the output.

答案2

得分: -1

在goroutine接收到第一个值之后,main()函数和goroutine之间存在一场竞争。在同步点之后,main()函数和goroutine之间的执行顺序是不确定的。

通过在代码中添加一些time.Sleep的调用,可以观察到这场竞争。如果goroutine在接收后休眠,那么很可能main()函数会赢得竞争,并在打印任何平方数之前退出。

func squares(c chan int) {
    for i := 0; i < 4; i++ {
        num := <-c
        time.Sleep(100)
        fmt.Println(num * num)
    }
}

如果main()函数在最后一次发送后休眠,那么很可能goroutine会赢得竞争并打印出四个平方数。

func main() {
    fmt.Println("main() started")
    c := make(chan int, 3)

    go squares(c)

    c <- 1
    c <- 2
    c <- 3
    c <- 4 

    time.Sleep(100)
    fmt.Println("main() stopped")
}

这只是程序的两种可能输出。在打印main() stopped之后,goroutine可以打印一个值,也可以打印零到四个值。

上面我使用了"可能"这个词,因为休眠并不能确保描述的结果。

英文:

There is a race between main() and the goroutine after the goroutine receives the first value. Any execution order between main() and the goroutine is possible after that synchronization point.

Sprinkle in some calls to time.Sleep to observe the race. If the goroutine sleeps after receive, then it's probable that main() wins the race and exits before any squares are printed.

func squares(c chan int) {
	for i := 0; i &lt; 4; i++ {
		num := &lt;-c
		time.Sleep(100)
		fmt.Println(num * num)
	}
}

https://go.dev/play/p/x-3wiWwFQGj

If main() sleeps after the last send, then it's probable that the goroutine wins the race and prints the four squares.

func main() {
	fmt.Println(&quot;main() started&quot;)
	c := make(chan int, 3)

	go squares(c)

	c &lt;- 1
	c &lt;- 2
	c &lt;- 3
	c &lt;- 4 

	time.Sleep(100)
	fmt.Println(&quot;main() stopped&quot;)
}

https://go.dev/play/p/HIpRKdbA5qa

These are just two possible outputs from the program. The goroutine can print a value after the main() stopped is printed and before main exits. The goroutine can print between zero and four values.

I say "probable" above because the sleeps do not ensure the outcome described.

huangapple
  • 本文由 发表于 2023年5月19日 20:53:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76289266.html
匿名

发表评论

匿名网友

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

确定