Go通道的行为似乎不一致。

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

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 (
	&quot;fmt&quot;
	)

func send(sendto chan string) {
	fmt.Println(&quot;send 1&quot;)
	sendto &lt;- &quot;Hello&quot;
	fmt.Println(&quot;send 2&quot;)
	sendto &lt;- &quot;World&quot;
	fmt.Println(&quot;send 3&quot;)
	sendto &lt;- &quot;&quot;
	fmt.Println(&quot;send() exit&quot;)
}

func main() {
	//hole := make(chan string)
	//hole := make(chan string, 0)
	hole := make(chan string, 1)
	go send(hole)
	fmt.Println(&quot;main loop&quot;)
	carryon := true
	for carryon {
		msg := &lt;- hole
		if msg == &quot;&quot; {
			carryon = false
		} else {
			fmt.Println(&quot; recd &quot;, 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 2recd Hello 之前打印,因为send()在运行到打印语句之前,runtime调度main()再次运行。

打印这两条消息之间没有先行发生关系。它们可以以任何顺序打印。

英文:

This timeline for the two goroutines shows what's going on:

send()                  main()

fmt.Println(&quot;send 1&quot;)
sendto &lt;- &quot;Hello&quot;       msg := &lt;- hole              // sender and receiver both ready
fmt.Println(&quot;send 2&quot;)
                        fmt.Println(&quot; recd &quot;, msg)  // msg is &quot;Hello&quot;
sendto &lt;- &quot;World&quot;       msg := &lt;- hole              // sender and receiver both ready
                        fmt.Println(&quot; recd &quot;, msg)  // msg is &quot;World&quot;
fmt.Println(&quot;send 3&quot;)
sendto &lt;- &quot;&quot;
fmt.Println(&quot;send() exit&quot;)

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.

huangapple
  • 本文由 发表于 2015年1月29日 03:11:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/28200468.html
匿名

发表评论

匿名网友

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

确定