sync.WaitGroup
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// work
}()
wg.Wait()
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
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"fmt"
"sync"
)
// main is where the program starts running
func main() {
// Declare wg with the zero value of its type
var wg sync.WaitGroup
// Loop: repeat while the condition holds
for i := 1; i <= 3; i++ {
wg.Add(1)
// Start this call in a new goroutine (runs concurrently)
go func(n int) {
// defer runs this call when the surrounding function returns
defer wg.Done()
// Print the values followed by a newline
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.
Example: Add Before, Done Inside
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"fmt"
"sync"
)
// Define the function process
func process(id int, wg *sync.WaitGroup) {
// defer runs this call when the surrounding function returns
defer wg.Done()
// Print the values followed by a newline
fmt.Println("processing item", id)
}
// main is where the program starts running
func main() {
// Declare wg with the zero value of its type
var wg sync.WaitGroup
// Loop: repeat while the condition holds
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
// Every Go file belongs to a package; main builds an executable
package main
// Import the packages this program uses
import (
"fmt"
"sync"
)
// Define the function worker
func worker(wg *sync.WaitGroup, results *[]int, mu *sync.Mutex, n int) {
// defer runs this call when the surrounding function returns
defer wg.Done()
mu.Lock()
*results = append(*results, n*n)
mu.Unlock()
}
// main is where the program starts running
func main() {
// Declare wg with the zero value of its type
var wg sync.WaitGroup
// Declare mu with the zero value of its type
var mu sync.Mutex
// Short declaration: create results and infer the type
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: