Master Go’s WaitGroup: Essential Guide to Safe Concurrent Programming
This article explains Go's sync.WaitGroup concurrency primitive, covering its core methods, a classic example, key characteristics, common pitfalls, and best‑practice rules to reliably wait for multiple goroutines to finish before proceeding.
In Go programming, the main routine often doesn't know when a goroutine finishes. To wait for goroutine completion before proceeding, use sync.WaitGroup.
1. Core Mechanism of WaitGroup
WaitGroup is a synchronization primitive with a counter and three methods:
Add(delta int): increments the counter; call before starting a goroutine.
Done(): decrements the counter, equivalent to Add(-1); usually deferred.
Wait(): blocks until the counter reaches zero.
The diagram below illustrates how the three methods cooperate to coordinate task start, completion, and waiting.
2. Simple Classic Example
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // ensure counter decrement
fmt.Printf("Worker %d starting
", id)
time.Sleep(time.Duration(id) * time.Second) // simulate work
fmt.Printf("Worker %d done
", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // increment counter
id := i // avoid race condition
go worker(id, &wg) // start goroutine
}
wg.Wait() // wait for all goroutines
fmt.Println("All workers done")
}The execution result is shown in the image below.
The workflow proceeds as follows: the main program adds to the counter, launches workers, each worker defers Done to decrement, and finally Wait blocks until the counter returns to zero, allowing subsequent logic to run.
3. Characteristics and Use Cases
WaitGroup embodies Go’s “minimal interface, maximal functionality” with these traits:
Minimal abstraction: only three methods achieve complex multi‑task synchronization.
Lightweight and efficient: built on atomic operations, often faster than channel‑based sync.
Concurrency safe: all methods are safe for concurrent calls.
Zero‑value usable: var wg sync.WaitGroup works without explicit initialization.
Typical scenarios include:
Waiting for all goroutines to exit.
Batch concurrent task synchronization.
Resource cleanup after operations.
Stage‑wise task coordination.
4. Common Mistakes and Pitfalls
Error 1: Counter set to a negative value
wg := sync.WaitGroup{}
wg.Add(5) // correct
wg.Add(-10) // wrong, panics because counter becomes negativeThe counter must never be negative; a negative value triggers a panic.
Error 2: Adding inside a goroutine
go func() {
wg.Add(1) // wrong: may occur after Wait
defer wg.Done()
// work...
}()
wg.Wait() // may return earlyAdd must be called before launching the goroutine to ensure the counter is ready.
Error 3: Forgetting Done
wg.Add(2)
go func() {
// work...
// missing wg.Done()
}()
go func() {
defer wg.Done()
}()
wg.Wait() // deadlockUse defer wg.Done() to guarantee the decrement even when errors occur.
Error 4: Wait does not block the main thread
func Start() {
var wg sync.WaitGroup
wg.Add(1)
go worker(&wg)
// function returns, wg may be reclaimed, causing undefined behavior
}Ensure the WaitGroup’s lifetime covers all goroutines; otherwise Done may operate on reclaimed memory.
Error 5: Reusing a WaitGroup before it finishes
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
wg.Done()
wg.Add(1) // wrong if Wait runs concurrently
}()
wg.Wait()Do not reuse a WaitGroup; create a new instance for each independent synchronization phase.
Error 6: Passing WaitGroup by value instead of pointer
// wrong: passes a copy, original counter unchanged
go worker(i, wg)
// correct: pass pointer to share the same counter
go worker(i, &wg)Always pass &wg to ensure all goroutines operate on the same counter.
Golden rules for safe WaitGroup usage:
Add before starting a goroutine.
Defer Done for reliable decrement.
Keep the WaitGroup alive until all tasks complete.
Never reuse a WaitGroup across unrelated tasks.
5. Summary
sync.WaitGroup is a fundamental Go concurrency tool that simplifies waiting for multiple operations to finish. When used correctly—adding before launch, deferring Done, managing lifecycle, and avoiding reuse—it enables robust, elegant concurrent code.
360 Zhihui Cloud Developer
360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.