How to Wait for All Goroutines to Finish Executing Before Continuing

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 your goroutine .

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