频道封锁顺序

I am trying to understand how channels work in golang. The code I have is very simple but the output is surprising.

As the documentation states: reading and writing from/to a channel is blocking the current goroutine, so I thought writing to a channel would block the channel until the main routine yields.

package main

func rtn(messages chan<- string) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"
}

func main() {
    messages := make(chan string)
    go rtn(messages)

    for msg := range messages {
        println(msg)
    }
}

I thought it would print

p1
ping1
p2
ping2

but it actually prints

p1
p2
ping1
ping2

You are using an unbuffered channels, that works as a point of synchronization between the main and second goroutines.

In this case, you only know that when the second goroutine is here messages <- "ping1" the main one is at the line for msg := range messages. Thus, there is no guarantee that main loop reaches println(msg) immediately. I.e., in the meantime the second goroutine could have moved on and reached lines println("p2") and messages <- "ping2".

As a counterexample, I am adding a channel just to enforce the complete synchronization between prints.

package main

func rtn(messages chan<- string, syncChan chan struct{}) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //Wait for main goroutine to print its message
    <-syncChan

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"

    //Wait for main goroutine to print its message
    <-syncChan
}

func main() {
    messages := make(chan string)
    syncChan := make(chan struct{})
    go rtn(messages, syncChan)

    for msg := range messages {
        println(msg)
        //Notify the second goroutine that is free to go
        syncChan <- struct{}{}
    }
}

Which prints the output you expected:

p1
ping1
p2
ping2

Here is another example that produce the output you are looking for. In this case the main goroutine is forcefully blocked by the time.Sleep(). This will make the second goroutine be ready to send before the receiver is ready to receive. Therefore, the sender will actually block on the send operation.

package main

import (
    "time"
)

func rtn(messages chan<- string) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"
}

func main() {
    messages := make(chan string)
    go rtn(messages)

    //Put main goroutine to sleep. This will make the
    //sender goroutine ready before the receiver. 
    //Therefore it will have to actually block!
    time.Sleep(time.Millisecond * 500)

    for msg := range messages {
        println(msg)
    }
}