Reputation: 816
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
Thank you
Upvotes: 0
Views: 176
Reputation: 42413
Roughly: Send and receive happen concurrently. The details are explained in the Go Memory Model which determines this behaviour. Concurrent code is complicated...
Upvotes: 3
Reputation: 120941
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.
Upvotes: 3
Reputation: 6545
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.
Upvotes: 1