sync.WaitGroup
sync.WaitGroup is a synchronization primitive used in Go's concurrent programming to wait for goroutines to finish. It uses a counter to block the main flow until all goroutines have completed.
Syntax
import "sync"
var wg sync.WaitGroup
// Increments the counter by N. Call this before launching a goroutine.
wg.Add(N)
// Decrements the counter by 1. The idiomatic way is to call it with defer inside a goroutine.
wg.Done()
// Blocks until the counter reaches 0.
wg.Wait()
// Typical pattern
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done() // Guaranteed to be called when the function exits.
// goroutine work here
}(i)
}
wg.Wait() // Waits until all goroutines have completed.
Method List
| Method | Description |
|---|---|
| Add(delta int) | Adds delta (positive or negative) to the counter. Call this before launching a goroutine. |
| Done() | Decrements the counter by 1. Equivalent to Add(-1). Call this when a goroutine finishes. |
| Wait() | Blocks the caller until the counter reaches 0. |
Sample Code
package main
import (
"fmt"
"sync"
"time"
)
// Processes an item concurrently.
func processItem(id int, wg *sync.WaitGroup) {
defer wg.Done() // Always calls Done() when the function exits.
fmt.Printf("Item %d: starting\n", id)
time.Sleep(time.Duration(id*50) * time.Millisecond) // Simulates processing time.
fmt.Printf("Item %d: done\n", id)
}
func main() {
var wg sync.WaitGroup
// Launch 5 goroutines.
for i := 1; i <= 5; i++ {
wg.Add(1) // Increment the counter before launching the goroutine.
go processItem(i, &wg)
}
fmt.Println("Waiting for all goroutines to finish...")
wg.Wait() // Blocks until every goroutine has called Done().
fmt.Println("All processing complete!")
fmt.Println()
// Pattern for safely collecting results (combined with a mutex).
var (
wg2 sync.WaitGroup
mu sync.Mutex
results []int
)
for i := 0; i < 5; i++ {
wg2.Add(1)
go func(n int) {
defer wg2.Done()
result := n * n
mu.Lock()
results = append(results, result) // Exclusive access via mutex.
mu.Unlock()
}(i)
}
wg2.Wait()
fmt.Println("Squared results (order not guaranteed):", results)
}
Notes
sync.WaitGroup is the most commonly used synchronization primitive for waiting on goroutines. Always pass a WaitGroup by pointer — never by value. Also, Add() must be called before launching a goroutine. Calling Add() inside a goroutine can cause a race condition.
A WaitGroup must be passed as a pointer (*sync.WaitGroup), not as a value. Copying it by value also copies its internal state, which breaks synchronization. Also, calling Done() more times than Add() will make the counter go negative and cause a panic.
For goroutine basics, see goroutine. For exclusive access to shared data, see sync.Mutex / RWMutex.
If you find any errors or copyright issues, please contact us.