AndyS
AndyS

Reputation: 816

Go Channels behaviour appears inconsistent

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

Answers (3)

Volker
Volker

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

Thundercat
Thundercat

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

Evan
Evan

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

Related Questions