0
0
GoHow-ToBeginner · 3 min read

How to Use sync.WaitGroup in Go for Goroutine Synchronization

In Go, use sync.WaitGroup to wait for a collection of goroutines to finish. Call Add(n) to set the number of goroutines, then each goroutine calls Done() when finished, and the main goroutine calls Wait() to block until all are done.
📐

Syntax

The sync.WaitGroup type helps you wait for multiple goroutines to finish. You use three main methods:

  • Add(n int): Set how many goroutines to wait for.
  • Done(): Call this inside each goroutine when it finishes.
  • Wait(): Call this to block until all goroutines have called Done().
go
var wg sync.WaitGroup
wg.Add(n) // n is number of goroutines

for i := 0; i < n; i++ {
    go func() {
        defer wg.Done()
        // goroutine work here
    }()
}

wg.Wait() // wait for all goroutines to finish
💻

Example

This example shows how to launch 3 goroutines that print messages and how sync.WaitGroup waits for all to finish before the program ends.

go
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(3) // we will wait for 3 goroutines

    for i := 1; i <= 3; i++ {
        go func(id int) {
            defer wg.Done() // mark this goroutine done when finished
            time.Sleep(time.Duration(id) * 500 * time.Millisecond) // simulate work
            fmt.Printf("Goroutine %d finished\n", id)
        }(i)
    }

    wg.Wait() // wait for all goroutines
    fmt.Println("All goroutines completed")
}
Output
Goroutine 1 finished Goroutine 2 finished Goroutine 3 finished All goroutines completed
⚠️

Common Pitfalls

Common mistakes when using sync.WaitGroup include:

  • Calling Done() fewer times than Add() causes Wait() to block forever.
  • Calling Add() after starting goroutines can cause race conditions.
  • Not using defer wg.Done() inside goroutines can lead to missed signals if the goroutine panics or returns early.
go
package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        // Forgot to call Done()
        fmt.Println("Goroutine running but Done not called")
    }()

    // This will block forever because Done() was never called
    // wg.Wait()

    // Correct way:
    // wg.Add(1)
    // go func() {
    //     defer wg.Done()
    //     fmt.Println("Goroutine running and Done called")
    // }()
    // wg.Wait()
}
Output
Goroutine running but Done not called
📊

Quick Reference

Remember these tips when using sync.WaitGroup:

  • Always call Add() before starting goroutines.
  • Use defer wg.Done() at the start of each goroutine.
  • Call Wait() to block until all goroutines finish.
  • Never call Wait() before Add() completes.

Key Takeaways

Use sync.WaitGroup to wait for multiple goroutines to finish safely.
Call Add(n) before starting goroutines to set the count.
Each goroutine must call Done() exactly once to signal completion.
Call Wait() to block until all goroutines have finished.
Use defer wg.Done() inside goroutines to avoid missing Done calls.