Go Tidbit: Waiting on Go Routines
Concurrency is one of the central features of Go. And, to build concurrent programs in Go, you need goroutines.
A goroutine is like a thread, but lighter. Much lighter. And, like any other built-in feature of Go, using it is dead simple:
|
|
Wait. That didn’t print anything.
This is because when the main
function of a Go program returns, it will abort all goroutines. Go will not wait until these goroutines finish running. But you can wait for goroutines to finish running.
In this blog post, we will take a look different ways of waiting for one or more goroutines to finish running.
Using a Channel
If you want to wait on one goroutine, you could go the primitive route: use a channel.
Create a channel, of any type. From within the goroutine, at the very end of the function, send a value to the channel. And where you want to wait for the goroutine to finish, read from the channel.
The read will block until there is something in the channel to read.
|
|
You could close the channel donech
instead of sending a value over it to signal multiple readers that the goroutine has finished running.
If you want to wait on multiple goroutines, then read from the channel as many times as there are goroutines.
|
|
In the example above, you are reading from the channel n
times. The loop, reading from the channel, can exit only when there are n
values to read from the channel.
The standard library has a neat abstraction for this: sync.WaitGroup
.
Using sync.WaitGroup
The sync.WaitGroup
type provides three methods:
Add(delta int)
: Add counts the number of goroutines we are waiting for.Done()
: Done decrements the count by 1.Wait()
: Wait blocks until the counter is zero.
Using a sync.WaitGroup
, you will Add
the number of goroutines to wait for. From within each goroutine, call Done
right before the function returns.
Finally, call Wait
from where you want to wait on the goroutines.
|
|
Until One of Several Goroutines Fails (Using errgroup.Group
)
If you have several goroutines and you want to stop as soon as any one of them experiences an error, then you could use Go’s almost standard package golang.org/x/sync/errgroup
.
This package provides the handy errgroup.Group
type that can run multiple goroutines and return the first non-nil error, if any.
|
|
Aborting the Rest
If your program doesn’t exit, the remaining goroutines will continue to run and consume resources. Depending on what these goroutines are doing you could use a cancellable context.Context
to signal the remaining goroutines to be aborted.
Until a Timeout
You can do wonderful things in Go with channels.
The time.NewTimer
function returns a time.Timer
with a channel. The channel is closed when the timer expires. By calling time.NewTimer
with a duration, the channel will close after the given duration has elapsed.
By combining multiple channel operations using a select
statement, you can wait on different conditions. For example, either wait on all goroutines to send values over a channel, or wait for a timer to expire.
|
|
In the code above, the second loop will exit when all goroutines have sent messages over donech
. Or, the timer t
has expired.
Similarly, you could wait until the user presses Ctrl+C
(i.e. sends an interrupt from the terminal) by using a signal channel.
Wrap Up
In Go, channels are the primitive for synchronization.
If you want to wait on a goroutine
you have to use a channel; whether it is as a primitive or through a package.
This post is 46th of my #100DaysToOffload challenge. Want to get involved? Find out more at 100daystooffload.com.
comments powered by Disqus