Lesson 10 / 25
sync.WaitGroup
Waiting for a set of goroutines to finish.
Add before go, Done on exit, Wait once
A sync.WaitGroup is a counter: Add(n) increases it, Done() decreases it by one, and Wait() blocks until it reaches zero. The key rule is to call Add before starting the goroutine, in the parent; calling Add inside the new goroutine races with Wait, which may see zero and return early. Put defer wg.Done() at the top of the goroutine so it runs even on early return. Pass the WaitGroup by pointer, never by value (copying it breaks it; go vet reports copied locks). Go 1.25 added wg.Go(f), which does the Add(1), starts f in a goroutine and calls Done when it returns (check that your Go version has it). A WaitGroup does not collect errors or cancel siblings; use errgroup for that.
Tools from the sync package
Waiting for groups, protecting shared state and doing things once, with low-level atomics underneath.
Classic form and the newer wg.Go form
Both wait for all workers; wg.Go requires Go 1.25 or later.
package main
import (
"fmt"
"sync"
)
func main() {
urls := []string{"a.example", "b.example", "c.example"}
// Classic: Add in the parent, Done deferred in the child.
var wg sync.WaitGroup
for _, u := range urls {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("fetch", u) // Go 1.22+: u is per-iteration
}()
}
wg.Wait()
// Go 1.25+: wg.Go wraps Add, go and Done.
var wg2 sync.WaitGroup
for _, u := range urls {
wg2.Go(func() {
fmt.Println("fetch again", u)
})
}
wg2.Wait()
}Loop variables before Go 1.22
Before Go 1.22, a for loop variable was shared across iterations, so closures often all saw the last value. Since Go 1.22 (when the module go line is 1.22 or later) each iteration gets a fresh variable. In older modules, pass the value as an argument: go func(u string) { ... }(u).
Quick check: Where should wg.Add(1) be called?
- In the parent goroutine, before the go statement
- Inside the new goroutine, as its first line
- After wg.Wait()
- Inside a deferred function
Answer
In the parent goroutine, before the go statement — Adding inside the goroutine races with Wait, which may return before the counter is incremented.