This code is a modified version of a program from section 8.4 of "The Go Programming Language" book.
package main
import (
"fmt"
)
func main() {
naturals := make(chan int)
squares := make(chan int)
done := make(chan struct{})
// counter
go func() {
for x := 0; x <= 10; x++ {
naturals <- x
}
done <- struct{}{}
}()
// squarer
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
// printer
go func() {
for x := range squares {
fmt.Println(x)
}
}()
<-done
naturals <- 90
close(naturals)
}
When I run this I get the output:
0 1 4 9 16 25 36 49 64 81 100
But when I change the last few lines to this:
naturals <- 90
<-done
I get the output:
8100 0 1 4 9 16 25 36 49 64 81
In the first case, when <-done
is before naturals <- 90
, I expected it to print all the squares of numbers 0 to 10, then print the square of 90, then close the channel and end the program.
In the second case, when naturals <- 90
is before <-done
, I expected 8100 to be printed somewhere before the squares of 0 to 10, but I also expected all of them to be printed.
Can someone help me get my head around what's happening?
So to break it down, the first question is why you're seeing 8100 printed first. That's quite simple: the routine that pushed values 0-10 to the naturals
channel is not started yet by the time you reach naturals <- 90
. The channels are not buffered, so that routine waits until the value 90
is read by the routine that squares the values, and only then will you be writing the values 0-10 to the channel. This isn't guaranteed to happen all the time, but starting a routine means the runtime (in particular the scheduler) has to do some work. This work takes a bit of time, during which the main routine starts another 2 routines, and writes to the channel. That's why 90 is the first value to go through the channel, rather than 0.
Next, you're noticing not all your values are printed. If you want all values to be printed, you should close, or write to the done
channel when your routine printing the output has completed. instead of having done <- struct{}{}
in the routine that writes to naturals
, you should move that to the routine where you read from squares
. To signal that the naturals
and squares
channels are "done", you can simply close them:
go func() {
for x := 0; x <= 10; x++ {
naturals <- x
}
close(naturals) // we'll get to this in a bit
}()
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
go func() {
for x := range squares {
fmt.Println(x)
}
done <- struct{}{}
}()
<-done // now we know everything has been printed
close(done) // always best to close all channels explicitly
Now this works like a treat, but we have a problem. There's no reliable, safe way to write our 90 value to the naturals
channel except for moving that to the routine where we write values 0-10. You could get around that problem by re-using the done
channel:
go func() {
for x := 0; x <= 10; x++ {
naturals <- x
}
done <- struct{}{} // signal this routine is done
}()
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
go func() {
for x := range squares {
fmt.Println(x)
}
close(done) // done channel is closed
}()
<-done // now we know everything has been printed
naturals <- 90 // now we can write to this channel
close(naturals) // signal to the square routine that was the last value
<-done // wait for the channel to close
This works, and will print the values in the order you want/expect, with 8100 as the last value, but it's a bit of a mess. Relying on a channel like done
here to mean different things is a bit of a pain. We're using it to signal both that we've written all our values to the naturals
channel, and then the second time to indicate that we've printed all values. That's smelly. Thankfully, we have other means of knowing when one or more routines have finished: sync.WaitGroup
:
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for x := 0; x <= 10; x++ {
naturals <- x
}
}()
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
go func() {
for x := range squares {
fmt.Println(x)
}
close(done) // just close it
}()
wg.Wait() // wait for the first routine to return
naturals <- 90 // now we can write to naturals
close(naturals) // we're done, let the squares routine return, and close its channel
// this in turn will trigger the printer routine to return
<- done // wait for everything to be printed
There's a lot more that can be said about this particular use of channels, who "owns" them, and what routine is responsible for closing which channel, why using buffered channels here might be a good idea (depending on what you're actually wanting to do/accomplish), etc... for now, though, this should help you get going and hopefully shed some light on what's going on.