WaitGroups
Use sync.WaitGroup to wait for a collection of goroutines to finish.
A sync.WaitGroup waits for a set of goroutines to complete. The main goroutine calls Add to set the number of goroutines to wait for, each goroutine calls Done when finished, and Wait blocks until the count reaches zero.
Call wg.Add(1) before launching each goroutine. Inside the goroutine, defer wg.Done() so the count decrements even if the goroutine panics. Call wg.Wait() to block until all goroutines finish.
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("worker %d starting\n", id)
// simulate work
fmt.Printf("worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("all workers done")
}Pass *sync.WaitGroup by pointer - copying a WaitGroup after first use is undefined behavior and will cause incorrect counts. wg.Add can be called with counts greater than one if you know the total up front, but adding inside the goroutine races with Wait:
// correct: Add before goroutine launch
wg.Add(1)
go func() {
defer wg.Done()
// ...
}()
// wrong: Add inside the goroutine may run after Wait
go func() {
wg.Add(1) // race: Wait may have already returned
defer wg.Done()
}()In production
Always pass *sync.WaitGroup by pointer - copying a WaitGroup after first use is a data race. The defer wg.Done() pattern ensures Done is called even if the goroutine panics. Add the count before launching the goroutine; adding inside the goroutine is a race.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free