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.
Remember that channels will block, so the select statement reads:
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 resultsThe 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 fromc
sofibonacci
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()
andgo func() ...
are running simultaneously! This is important since we're using a producer/consumer pattern in this code.fibonacci
produces values and sends them toc
, and the anonymous goroutine that's spawned from main consumes values fromc
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, becausec
will block. Furthermorefibonacci
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.You've pretty much got it.
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
Yes, the go keyword starts a goroutine, so the func() will run at the same time as the fibonacci(c, quit). The receive from the channel in the Println simply blocks until there is something to receive
There are two key things to understanding this code example:
First, let's review how unbuffered channels work. From the documentation
Note that both channels in the code example,
c
andquit
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 byfunc main()
, and the routine started bygo func()...
inside thefunc main()
.I added some inline comments here which should make things clearer: package main import "fmt"