go tutorial select statement

Multi tool use
go tutorial select statement
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.
3 Answers
3
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."
default
Then in your main process you spin off another function that reads from c
to print the results
c
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.
go
c
fibonacci
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.
go
main()
go func() ...
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.
fibonacci
c
c
c
fibonacci
Ok, I think I understand the switch statement, but can you explain what you mean by: "Using go to spin off the second function lets you consume from c so fibonacci will continue."
– Touchdown
Jan 21 '16 at 18:19
@Touchdown I re-read the question and noticed your confusion about goroutines. I've edited a small explanation
– Adam Smith
Jan 21 '16 at 18:22
Thanks, your explanation is a lot clearer than what's on the tutorial!
– Touchdown
Jan 21 '16 at 18:28
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
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.
c
quit
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()
.
go
func main()
go func()...
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()
}
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Your question regarding "go func() // when does this get called?" --- it gets called right there - notice the () at the end. This is an inline function/closure and "go" runs it async. Two threads talking to each other. It is running in the background - it reads from c (which fibonacci is writing to) and once it has read 10 items it writes to quit - telling fibonacci it is done and can exit. The inline blocks waiting for data on c. Execution then continues by calling fibonacci() which does the work. You might read this and poke around other articles. blog.golang.org/pipelines
– ripvlan
Jan 21 '16 at 18:25