Take a look at this code, what do you think it will print?
func main() {
ch := make(chan int)
defer close(ch)
go func() {
v := <-ch
fmt.Println("value read is ", v)
}()
time.Sleep(2 * time.Second)
ch <- 42
}
It will not print anything.
And it will exit without any deadlock errors.
Here’s why:
Once we read the value of the channel in “v”, the main goroutine will stop waiting for child goroutine and it will exit, thus skipping executing this line:
fmt.Println("value read is ", v)
The correct way is to use a sync.WaitGroup, like so. The code bellow will print “value read is 42”:
func main() {
ch := make(chan int)
defer close(ch)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("started goroutine")
v := <-ch
fmt.Println("value read is ", v)
}()
time.Sleep(2 * time.Second)
ch <- 42
wg.Wait()
}
It helps if we use an analogy. Go is like a tennis arbiter. Once the match is over, it will exit.
In our first example we start the match by writing to channel, thus serving the ball. In the child goroutine we receive the ball. Go sees this and concludes that the match is over, thus exiting.
In our second example we’re using a colored flag to signal the arbiter we’re done (the sync.WaitGroup Done() function). Since Done() is called at the end of the child goroutine, the print statement has a chance to execute.