I'm learning the Go language. Here is an example I've come across. Can someone please explain what is happening here?
package main
import "time"
import "fmt"
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(time.Second * 1)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
default:
fmt.Println("Default")
}
}
}
Output:
Default
Default
Program Exited
If I comment out the default section
//default:
// fmt.Println("Default")
the output becomes:
received one
received two
Program exited.
How does the presence of the default
case change the way channel blocking works?
Closing a channel indicates that no more values will be sent on it. This can be useful to communicate completion to the channel's receivers. In this example we'll use a jobs channel to communicate work to be done from the main() goroutine to a worker goroutine.
In Golang, or Go, channels are a means through which different goroutines communicate. Think of them as pipes through which you can connect with different concurrent goroutines. The communication is bidirectional by default, meaning that you can send and receive values from the same channel.
Blocking Statements This means: A statement to receive data from a channel will block until some data is received. A statement to send data to a channel will wait until the sent data has been received.
Golang provides buffered channels, which allow you to specify a fixed length of buffer capacity so one can send that number of data values at once. These channels are only blocked when the buffer is full. Likewise, the channel on the receiving end will only block when the buffer is empty.
This is related to how select
statements work in Go.
From the Go documentation on select
:
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
So, without a default case, the code will block until some data is available in either of the channels. It implicitly waits for the other goroutines to wake up and write to their channel.
When you add the default case, it is very likely that the select
statement is reached before the other goroutines wake up from sleeping.
So, since there is no data available (yet), and there is a default case, the default case is executed. This is done twice, and it takes less than 1 second. So the program ends up terminating before any of the go routines have a chance to wake up and write to the channel.
Note that this is technically a race condition; there is absolutely no guarantee that the 2 iterations of the loop will run before any of the go routines wake up, so in theory it is possible to have a different output even with a default case, but in practice it is extremely unlikely.
The select
statement blocks until at least one case is ready. The Go language specification reads, in part:
If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection. Otherwise, if there is a default case, that case is chosen. If there is no default case, the "select" statement blocks until at least one of the communications can proceed.
In the original code, the default
case is ready on both iterations of the loop because there is a delay before anything is sent on c1
or c2
.
After you remove the default
case, the select
statement must wait for data to be available in c1
or c2
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With