英文:
Go Channels behaviour appears inconsistent
问题
我注意到了unbuffered channels在工作方式上存在不一致性的问题 - 这可能是Go语言本身的不一致性,也可能是我对Go语言的理解有误...
这里有一个简单的例子和输出。不一致性出现在"make channel"这行代码上。
package main
import (
"fmt"
)
func send(sendto chan string) {
fmt.Println("send 1")
sendto <- "Hello"
fmt.Println("send 2")
sendto <- "World"
fmt.Println("send 3")
sendto <- ""
fmt.Println("send() exit")
}
func main() {
//hole := make(chan string)
//hole := make(chan string, 0)
hole := make(chan string, 1)
go send(hole)
fmt.Println("main loop")
carryon := true
for carryon {
msg := <- hole
if msg == "" {
carryon = false
} else {
fmt.Println(" recd ", msg)
}
}
}
当我按照上述方式运行时,输出结果是符合预期的(对于缓冲区大小为2的情况也是如此)。也就是说,通道的缓冲区大小为1,可以容纳一个值 - 在尝试写入下一个值时,会发生上下文切换到主线程,以便它可以消费第一个值。
main loop
send 1
send 2
recd Hello
send 3
recd World
send() exit
然而,当我将"make channel"这行代码改为:
hole := make(chan string, 0)
输出结果变为:
main loop
send 1
send 2
recd Hello
recd World
send 3
send() exit
我本来期望"send 2"和"recd Hello"的顺序是相反的...
对于hole := make(chan string)
,我得到了相同的输出结果。
我查看了规范,它说:
容量(以元素个数表示)设置了通道缓冲区的大小。如果容量为零或者省略,则通道是无缓冲的,只有在发送方和接收方都准备好时通信才会成功。否则,通道是有缓冲的,如果缓冲区不满(发送)或不空(接收),通信就会成功而不会阻塞。
请问有人可以解释一下:
- 为什么我的期望是错误的 - 请友善地解释
- 或者Go语言是否实际上是错误的
谢谢!
英文:
I am seeing an inconsistency in the way unbuffered channels appear to work - this is either an inconsistency in Go, or in my understanding of Go...
Here is a simple example with output. The 'inconsistency' is with the 'make channel' lines.
package main
import (
"fmt"
)
func send(sendto chan string) {
fmt.Println("send 1")
sendto <- "Hello"
fmt.Println("send 2")
sendto <- "World"
fmt.Println("send 3")
sendto <- ""
fmt.Println("send() exit")
}
func main() {
//hole := make(chan string)
//hole := make(chan string, 0)
hole := make(chan string, 1)
go send(hole)
fmt.Println("main loop")
carryon := true
for carryon {
msg := <- hole
if msg == "" {
carryon = false
} else {
fmt.Println(" recd ", msg)
}
}
}
When I run as above, the output is as expected (and also as expected for a buffer size of 2). i.e. the channel has a buffer of 1, which holds one value - on next attempting to write, there is a context switch to the main to allow it to consume the first value.
main loop
send 1
send 2
recd Hello
send 3
recd World
send() exit
When I then change the make channel line to:
hole := make(chan string, 0)
The output is:
main loop
send 1
send 2
recd Hello
recd World
send 3
send() exit
I would have expected the send 2
and the recd Hello
to be the other way around...
I get the same output for hole := make(chan string)
I checked the specification and it says
> The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives).
Please can someone explain either
- Why my expectations are wrong - please be kind
- or whether Go is actually wrong
Thank you
答案1
得分: 3
大致来说:发送和接收是同时发生的。具体的细节在Go内存模型中有解释,它决定了这种行为。并发代码是复杂的...
英文:
Roughly: Send and receive happen concurrently. The details are explained in the Go Memory Model which determines this behaviour. Concurrent code is complicated...
答案2
得分: 3
这两个goroutine的时间轴展示了正在发生的事情:
send() main()
fmt.Println("send 1")
sendto <- "Hello" msg := <- hole // 发送者和接收者都准备好了
fmt.Println("send 2")
fmt.Println(" recd ", msg) // msg 是 "Hello"
sendto <- "World" msg := <- hole // 发送者和接收者都准备好了
fmt.Println(" recd ", msg) // msg 是 "World"
fmt.Println("send 3")
sendto <- ""
fmt.Println("send() exit")
send 2
在 recd Hello
之前打印,因为send()在运行到打印语句之前,runtime调度main()再次运行。
打印这两条消息之间没有先行发生关系。它们可以以任何顺序打印。
英文:
This timeline for the two goroutines shows what's going on:
send() main()
fmt.Println("send 1")
sendto <- "Hello" msg := <- hole // sender and receiver both ready
fmt.Println("send 2")
fmt.Println(" recd ", msg) // msg is "Hello"
sendto <- "World" msg := <- hole // sender and receiver both ready
fmt.Println(" recd ", msg) // msg is "World"
fmt.Println("send 3")
sendto <- ""
fmt.Println("send() exit")
send 2
is printed before recd Hello
because send() runs to the print statement before the runtime schedules main() to run again.
There is no happens before relationship for printing the two messages. They can be printed in either order.
答案3
得分: 1
通信只有在发送方和接收方都准备好时才会成功。关键在于接收方不需要立即开始处理接收到的消息。在你的情况下,接收方已经准备好了,所以它在不调用调度器(无上下文切换)的情况下接收到了值。协程继续运行,直到再次尝试发送时,接收方不再准备好,这时调度器会被调用。
英文:
> communication succeeds only when both a sender and receiver are ready
The key is that this does not require the receiver to immediately start processing the message it has received. Specifically in your case, it is ready, so it receives the value without invoking the scheduler (no context switch). The goroutine continues running until it tries to send again, at which point the receiver is not ready, so the scheduler is invoked etc.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论