Understanding WaitGroups
Let’s dive straight in and look at what a WaitGroup
is and what problem it solves for us.
When you start writing applications in Go that leverage goroutines
, you start hitting scenarios where you need to block the execution of certain parts of your code base, until these goroutines
have successfully executed.
Take for example this code:
package main
import "fmt"
func myFunc() {
fmt.Println("Inside my goroutine")
}
func main() {
fmt.Println("Hello World")
go myFunc()
fmt.Println("Finished Execution")
}
It first prints out Hello World
before triggering a goroutine
and then finally printing out Finished Execution
.
When you run this however, you should notice that when you run this program, it fails to reach line 6
and it never actually prints out Inside my goroutine
. This is because the main function actually terminates before the goroutine
gets a chance to execute.
The Solution? - WaitGroups
WaitGroups essentially allow us to tackle this problem by blocking until any goroutines within that WaitGroup
have successfully executed.
We first call .Add(1)
on our WaitGroup
to set the number of goroutines
we want to wait for, and subsequently, we call .Done()
within any goroutine
to signal the end of its’ execution.
Note - You need to ensure that you call
.Add(1)
before you execute yourgoroutine
.
A Simple Example
Now that we’ve covered the essential theory, let’s take a look at how we can fix our previous example through the use of WaitGroups
:
package main
import (
"fmt"
"sync"
)
func myFunc(waitgroup *sync.WaitGroup) {
fmt.Println("Inside my goroutine")
waitgroup.Done()
}
func main() {
fmt.Println("Hello World")
var waitgroup sync.WaitGroup
waitgroup.Add(1)
go myFunc(&waitgroup)
waitgroup.Wait()
fmt.Println("Finished Execution")
}
As you can see, we’ve instantiated a new sync.WaitGroup
and then called the .Add(1)
method, before attempting to execute our goroutine
.
We’ve updated the function to take in a pointer to our existing sync.WaitGroup
and then called the .Done()
method once we have successfully finished our task.
Finally, on line 19
, we call waitgroup.Wait()
to block the execution of our main()
function until the goroutines
in the waitgroup have successfully completed.
When we run this program, we should now see the following output:
$ go run main.go
Hello World
Inside my goroutine
Finished Execution
Anonymous Functions
It should be noted that we can accomplish the same thing as above using anonymous functions should we so wish. This can be more succinct and easier to read if the goroutine itself isn’t too complex:
package main
import (
"fmt"
"sync"
)
func main() {
fmt.Println("Hello World")
var waitgroup sync.WaitGroup
waitgroup.Add(1)
go func() {
fmt.Println("Inside my goroutine")
waitgroup.Done()
}()
waitgroup.Wait()
fmt.Println("Finished Execution")
}
Again, if we run this it provides the same results:
$ go run main.go
Hello World
Inside my goroutine
Finished Execution