sync.WaitGroup
Waiting for Goroutines to Finish
sync.WaitGroup lets the main goroutine wait until a group of other goroutines have all finished, replacing fragile approaches like time.Sleep with a precise, deterministic signal.
Example: Waiting for Goroutines to Finish
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
fmt.Println("task", n, "complete")
}(i)
}
wg.Wait()
fmt.Println("all tasks finished")
}
Login to try C/C++/Java/PHP code in the editor
Add Before, Done Inside
Add(1) should be called once for each goroutine right before it's launched, from the main goroutine -- calling it from inside the new goroutine risks Wait() returning before Add has even run.
Note: Always defer wg.Done() as the very first line inside the goroutine, so it runs even if the goroutine panics.
Example: Add Before, Done Inside
package main
import (
"fmt"
"sync"
)
func process(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("processing item", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go process(i, &wg)
}
wg.Wait()
fmt.Println("done")
}
Login to try C/C++/Java/PHP code in the editor
Passing WaitGroup by Pointer
A sync.WaitGroup must never be copied after first use -- always pass it around as a pointer (*sync.WaitGroup), since copying it would give different parts of the code independent, inconsistent counters.
Example: Passing WaitGroup by Pointer
package main
import (
"fmt"
"sync"
)
func worker(wg *sync.WaitGroup, results *[]int, mu *sync.Mutex, n int) {
defer wg.Done()
mu.Lock()
*results = append(*results, n*n)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
results := []int{}
for i := 1; i <= 4; i++ {
wg.Add(1)
go worker(&wg, &results, &mu, i)
}
wg.Wait()
fmt.Println(results)
}
Login to try C/C++/Java/PHP code in the editor
- Calling wg.Add() inside the goroutine instead of before launching it, creating a race on the counter's initial value.
- Forgetting to call wg.Done() (often via defer) inside each goroutine, causing Wait() to block forever.
- Copying a sync.WaitGroup by value into a function instead of passing a pointer, which silently breaks its counting.
- sync.WaitGroup tracks a count of outstanding goroutines to wait for.
- Add(n) increases the counter, Done() decrements it, and Wait() blocks until it reaches zero.
- Call Add() before launching each goroutine, not from inside it, to avoid a race.
- Always pass a *sync.WaitGroup (or hold it by reference) -- copying it breaks the count.
Chapter Quiz — Complete all 8 topics to unlock
0/8 topics done
Complete these topics first: