I'm working through the examples at tour.golang.org, and I've encountered this code I don't really understand:
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x: // case: send x to channel c?
x, y = y, x+y
case <-quit: // case: receive from channel quit?
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() { // when does this get called?
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
I understand the basics of how channels work, but what I don't get is how the above select statement works. The explanation on the tutorial says:
"The select statement lets a goroutine wait on multiple communication operations. A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready."
But how are the cases getting executed? From what I can tell, they're saying:
case: send x to channel c
case: receive from quit
I think I understand that the second one executes only if quit has a value, which is done later inside the go func(). But what is the first case checking for? Also, inside the go func(), we're apparently printing values from c, but c shouldn't have anything in it at that point? The only explanation I can think of is that the go func() somehow executes after the call to fibonacci(). I'm guessing it's a goroutine which I don't fully understand either, it just seems like magic.
I'd appreciate if someone could go through this code and tell me what it's doing.
The select statement lets a goroutine wait on multiple communication operations. A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.
You can have any number of case statements within a select.
GoLang select statement is like the switch statement, which is used for multiple channels operation. This statement blocks until any of the cases provided are ready.
The <- operator represents the idea of passing a value from a channel to a reference.
Remember that channels will block, so the select statement reads:
select {
case c <- x: // if I can send to c
// update my variables
x, y = y, x+y
case <-quit: // If I can receive from quit then I'm supposed to exit
fmt.Println("quit")
return
}
The absence of a default
case means "If I can't send to c and I can't read from quit, block until I can."
Then in your main process you spin off another function that reads from c
to print the results
for i:=0; i<10; i++ {
fmt.Println(<-c) // read in from c
}
quit <- 0 // send to quit to kill the main process.
The key here is to remember that channels block, and you're using two unbuffered channels. Using go
to spin off the second function lets you consume from c
so fibonacci
will continue.
Goroutines are so-called "green threads." Starting a function call with the keyword go
spins it off into a new process that runs independent of the main line of execution. In essence, main()
and go func() ...
are running simultaneously! This is important since we're using a producer/consumer pattern in this code.
fibonacci
produces values and sends them to c
, and the anonymous goroutine that's spawned from main consumes values from c
and processes them (in this case, "processing them" just means printing to the screen). We can't simply produce all the values and then consume them, because c
will block. Furthermore fibonacci
will produce more values forever (or until integer overflow anyway) so even if you had a magic channel that had an infinitely long buffer, it would never get to the consumer.
There are two key things to understanding this code example:
First, let's review how unbuffered channels work. From the documentation
If the channel is unbuffered, the sender blocks until the receiver has received the value.
Note that both channels in the code example, c
and quit
are unbuffered.
Secondly, when we use the go
keyword to start a new goroutine, the execution will happen in parallel with other routines. So in the example, we have two go routines running: the routine started by func main()
, and the routine started by go func()...
inside the func main()
.
I added some inline comments here which should make things clearer: package main import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for { // this is equivalent to a while loop, without a stop condition
select {
case c <- x: // when we can send to channel c, and because c is unbuffered, we can only send to channel c when someone tries to receive from it
x, y = y, x+y
case <-quit: // when we can receive from channel quit, and because quit is unbuffered, we can only receive from channel quit when someone tries to send to it
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() { // this runs in another goroutine, separate from the main goroutine
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit) // this doesn't start with the go keyword, so it will run on the go routine started by func main()
}
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