[Golang (Go)] Use sync.WaitGroup to wait for all the goroutines finish

sync.WaitGroup

WaitGroup is used to wait for all the goroutines launched here to finish since the main function actually terminates before the goroutine gets a chance to execute by defatult.

WaitGroups essentially allow us to tackle this problem by blocking until any goroutines within that WaitGroup have successfully executed.

We first call .Add(n) on our WaitGroup to set the number n of goroutines we want to wait for, and subsequently, we call .Done() within any goroutine to signal the end of its’ execution.

Package sync provides basic synchronization primitives such as mutual exclusion locks. Other than the Once and WaitGroup types, most are intended for use by low-level library routines. Higher-level synchronization is better done via channels and communication.

Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// waitgroups.go

package main

import (
"fmt"
"sync"
"time"
)

func worker(id int, wg *sync.WaitGroup) {

defer wg.Done() // In the coroutine group, at the end of each coroutine, execute the method .Done().

fmt.Printf("Worker %d starting\n", id)

time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}

func main() {

var wg sync.WaitGroup // Declare the WaitGroup variable

for i := 1; i <= 5; i++ {
wg.Add(1) // Execute the .Add(n) method. There are n coroutine groups, execute Add(n)
go worker(i, &wg)
}

wg.Wait() // Behind the coroutine group, execute the .Wait() method
}

Run it will output:

1
2
3
4
5
6
7
8
9
10
11
$ go run waitgroups.go
Worker 5 starting
Worker 3 starting
Worker 4 starting
Worker 1 starting
Worker 2 starting
Worker 4 done
Worker 1 done
Worker 2 done
Worker 5 done
Worker 3 done

FAQs

The counter is negative

The value of the WaitGroup counter must be greater than or equal to 0, otherwise a panic error will occur.

In general, there are two scenarios that will cause the WaitGroup counter to be negative.

  • Directly set the counter to a negative value through the .Add() function. For example, if you execute Add(-1) when it comes up, the program will panic and exit. This situation is relatively simple and the possibility of error is relatively low.
1
2
3
4
func main() {
var wg sync.WaitGroup
wg.Add(-1) // Error: panic and exit, the counter is a negative.
}
  • The .Done() function is executed too much, causing the counter to become negative. The correct way to use WaitGroup is to first determine the value of the WaitGroup counter (the size of the coroutine group), and then call the .Done() function the same number of times. If .Done() is called too many times, the counter value will be negative and panic will occur.
1
2
3
4
5
6
7
func main() {
var wg sync.WaitGroup
wg.Add(1)
wg.Done()
wg.DOne() // Error: panic and exit, is called too many times.
wg.Wait()
}

Of course, you can’t call it less, otherwise deadlock will occur.

1
2
3
4
5
6
func main() {
var wg sync.WaitGroup
wg.Add(2)
wg.DOne()
wg.Wait() // Deadlock.
}

.Add() execute after .Wait()

This question says that all .Add() executions need to be ensured before .Wait() is executed, otherwise, .Add() has not been executed yet, .Wait() is executed first, and if the counter value is equal to 0, proceed directly to the next step.

1
2
3
4
5
func main() {
var wg sync.WaitGroup
wg.Wait() // The counter value is equal to 0, proceed directly without blocking.
wg.Add(1)
}

Reuse previous WaitGroup whose .Wait() is not there yet

The previous Wait() is not there yet, just reuse WaitGroup

WaitGroup can be reused. As long as the value of the WaitGroup counter becomes 0, it can be regarded as a newly created WaitGroup and be reused.

But before repeated use, you must wait until the previous Wait is completed before you can execute .Add() or
.Done() and other functions, otherwise it will report “unusable error”.

1
2
3
4
5
6
7
8
9
10
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
time.Sleep(time.Millisecond)
wg.Done() // Counter--
wg.Add(1) // Counter++
}()
wg.Wait() // May be error - sync: WaitGroup is reused before previous Wait has returned。
}

References

[1] WaitGroup | sync - The Go Programming Language - https://golang.org/pkg/sync/#example_WaitGroup

[2] Go by Example: WaitGroups - https://gobyexample.com/waitgroups

[3] Go WaitGroup Tutorial | TutorialEdge.net - https://tutorialedge.net/golang/go-waitgroup-tutorial/

[4] Using Synchronization Primitives in Go | by Abhishek Gupta | Better Programming - https://betterprogramming.pub/using-synchronization-primitives-in-go-mutex-waitgroup-once-2e50359cb0a7