LibraryWaitGroups for Synchronizing Goroutine Completion

WaitGroups for Synchronizing Goroutine Completion

Learn about WaitGroups for Synchronizing Goroutine Completion as part of Go Programming for Backend Systems

Synchronizing Goroutine Completion with WaitGroups

In Go, when you launch multiple goroutines to perform concurrent tasks, you often need a way to ensure that the main program doesn't exit before all goroutines have finished their work. This is where

code
sync.WaitGroup
becomes invaluable. It provides a simple yet powerful mechanism to wait for a collection of goroutines to complete.

What is a WaitGroup?

code
sync.WaitGroup
is a struct in Go's standard library that acts as a counter. You increment the counter for each goroutine you start, and then decrement it when a goroutine finishes its task. The
code
Wait()
method blocks until the counter becomes zero, signaling that all goroutines have completed.

WaitGroup is a counter for goroutine completion.

WaitGroup helps the main program wait for all launched goroutines to finish their tasks before proceeding or exiting.

The sync.WaitGroup type has three primary methods:

  1. Add(delta int): Increments the WaitGroup counter by delta. Typically, you call Add(1) before launching each goroutine.
  2. Done(): Decrements the WaitGroup counter by one. This should be called by each goroutine when it finishes its work, often using defer to ensure it's called even if the goroutine panics.
  3. Wait(): Blocks until the WaitGroup counter is zero. The main goroutine or any other goroutine that needs to wait for the completion of the group calls this method.

How to Use WaitGroups

Using

code
WaitGroup
involves a clear pattern: initialize a
code
WaitGroup
, add to it for each goroutine, launch the goroutines (each calling
code
Done()
when finished), and finally, call
code
Wait()
in the goroutine that needs to synchronize.

Loading diagram...

What are the three core methods of sync.WaitGroup and what do they do?

Add(delta int) increments the counter, Done() decrements the counter, and Wait() blocks until the counter is zero.

It's crucial to call

code
wg.Add()
before launching the goroutine. If you call it inside the goroutine, there's a race condition where
code
wg.Wait()
might be called before
code
wg.Add()
has a chance to increment the counter, leading to unexpected behavior or premature exit.

Always call wg.Add() before launching the goroutine to avoid race conditions.

Similarly,

code
defer wg.Done()
is the idiomatic way to ensure
code
Done()
is called. This guarantees that the counter is decremented when the goroutine exits, regardless of how it exits (normal completion, panic, etc.).

Example Scenario

Imagine a web scraper that needs to fetch data from multiple URLs concurrently. You can use

code
WaitGroup
to ensure all URLs are processed before the scraper reports completion or moves to the next stage.

Consider a scenario where you want to process a list of numbers concurrently. Each goroutine will square a number and print it. A WaitGroup ensures the main function waits for all squaring operations to finish.

package main

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

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Signal that this goroutine is done
	fmt.Printf("Worker %d starting\n", id)
	// Simulate some work
	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) // Increment counter for each goroutine
		go worker(i, &wg) // Launch goroutine
	}

	wg.Wait() // Block until all goroutines are done
	fmt.Println("All workers have finished.")
}

In this code, wg.Add(1) is called before go worker(i, &wg). Inside worker, defer wg.Done() ensures the counter is decremented. Finally, wg.Wait() in main pauses execution until all 5 workers have called Done().

📚

Text-based content

Library pages focus on text content

Key Takeaways

Using

code
sync.WaitGroup
is fundamental for managing concurrent tasks in Go. Remember to:

  • Initialize a
    code
    WaitGroup
    .
  • Call
    code
    Add(1)
    before launching each goroutine.
  • Use
    code
    defer wg.Done()
    inside each goroutine.
  • Call
    code
    wg.Wait()
    in the goroutine that needs to synchronize.

Learning Resources

Go Concurrency Patterns: WaitGroup(blog)

This official Go blog post discusses concurrency patterns, including a practical example of using WaitGroups in pipeline processing.

Go WaitGroup Explained(tutorial)

A clear and concise tutorial explaining the purpose and usage of sync.WaitGroup with code examples.

Go `sync.WaitGroup` Tutorial(tutorial)

Provides a step-by-step guide on how to use WaitGroup, covering Add, Done, and Wait methods with illustrative code.

Understanding Go's WaitGroup(blog)

A Medium article that delves into the mechanics of WaitGroup and common pitfalls to avoid.

Go Concurrency: WaitGroups(video)

A video tutorial demonstrating the use of WaitGroups in Go for synchronizing goroutines.

Go `sync` Package Documentation(documentation)

The official Go documentation for the `sync` package, detailing the `WaitGroup` type and its methods.

Effective Go: Concurrency(documentation)

Part of the official Go documentation, this section covers concurrency primitives, including WaitGroups, in the context of writing effective Go code.

Go Goroutines and WaitGroups(blog)

GeeksforGeeks provides an explanation of goroutines and how WaitGroups are used to manage their execution flow.

Concurrency in Go: WaitGroups(blog)

An in-depth article from Ardan Labs discussing the nuances and best practices for using WaitGroups in Go applications.

Go WaitGroup Example(tutorial)

A straightforward example demonstrating a common use case for WaitGroup in Go.