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)
}
}