Use sync.WaitGroup to track a set of goroutines by incrementing the counter before launching each one and decrementing it when finished, then call Wait() to block until the counter reaches zero. This ensures your main function doesn't exit prematurely while background tasks are still running.
Here is a standard pattern for launching multiple workers:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Decrement counter when function returns
fmt.Printf("Worker %d starting\n", id)
time.Sleep(1 * time.Second) // Simulate work
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
// Launch 3 goroutines
for i := 1; i <= 3; i++ {
wg.Add(1) // Increment counter before starting
go worker(i, &wg)
}
// Block until all workers call Done()
wg.Wait()
fmt.Println("All workers finished")
}
If you need to wait for a specific number of goroutines without a loop, you can manually add them:
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
// Do work
}()
go func() {
defer wg.Done()
// Do work
}()
wg.Wait()
Key things to remember: always call Add() before starting the goroutine to avoid race conditions, and use defer wg.Done() inside the goroutine to ensure the counter decrements even if the function panics. Never call Add() after the goroutine has started unless you are sure the counter hasn't been decremented yet, as WaitGroup is not safe for concurrent modification of the counter itself.