Start Coding

Topics

Go WaitGroups: Synchronizing Goroutines

WaitGroups are a powerful synchronization primitive in Go, used to coordinate multiple goroutines. They allow you to wait for a collection of goroutines to finish executing before moving on to the next part of your program.

Understanding WaitGroups

A WaitGroup is a struct from the sync package. It maintains a counter of active goroutines and provides methods to increment, decrement, and wait for the counter to reach zero.

Key Methods

  • Add(delta int): Increments the counter by the specified delta
  • Done(): Decrements the counter by 1
  • Wait(): Blocks until the counter becomes zero

Using WaitGroups

Here's a simple example demonstrating how to use WaitGroups:


package main

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

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("All workers completed")
}
    

In this example, we create five worker goroutines and use a WaitGroup to ensure they all complete before the main function exits.

Best Practices

  • Always call wg.Add() before starting a new goroutine
  • Use defer wg.Done() at the beginning of goroutine functions to ensure it's called even if the function panics
  • Pass WaitGroup pointers to functions to avoid copying
  • Ensure the number of Done() calls matches the number of Add() calls

WaitGroups vs Channels

While both WaitGroups and channels can be used for synchronization, they serve different purposes:

WaitGroups Channels
Simple synchronization Communication and synchronization
Wait for multiple goroutines Pass data between goroutines
No data passing Can be used for signaling completion

Advanced Example: Parallel Processing

Here's a more complex example using WaitGroups for parallel processing:


package main

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

func processItem(item int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(time.Duration(item) * time.Millisecond)
    results <- item * 2
}

func main() {
    items := []int{2, 4, 6, 8, 10}
    results := make(chan int, len(items))
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        go processItem(item, results, &wg)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    for result := range results {
        fmt.Printf("Received result: %d\n", result)
    }
}
    

This example demonstrates how to combine WaitGroups with channels for parallel processing and result collection.

Conclusion

WaitGroups are an essential tool for managing concurrent operations in Go. They provide a simple and effective way to synchronize multiple goroutines, ensuring that all tasks complete before moving on. By mastering WaitGroups, you'll be better equipped to handle complex concurrent scenarios in your Go programs.