Working with WaitGroups in Go

Table of Contents

  1. Overview
  2. Prerequisites
  3. Setting Up Go
  4. Understanding WaitGroups
  5. Using WaitGroups in Go
  6. Example: Parallel Image Processing
  7. Conclusion

Overview

In Go, concurrency is achieved using goroutines, which are lightweight threads of execution. However, when working with goroutines, we often need to wait for all of them to complete before proceeding further. This is where WaitGroups come in handy. WaitGroups provide a mechanism to wait for a collection of goroutines to finish their execution.

In this tutorial, we will explore how to work with WaitGroups in Go. By the end of this tutorial, you will have a clear understanding of how to use WaitGroups to coordinate the execution of multiple goroutines and handle their synchronization.

Prerequisites

To follow along with this tutorial, you should have a basic understanding of the Go programming language and its syntax. If you are new to Go, you can refer to the Go Tour or the official Go documentation to familiarize yourself with the language.

Setting Up Go

Before we dive into using WaitGroups, make sure you have Go installed on your machine. You can download Go from the official website (https://golang.org/dl/) and follow the installation instructions for your operating system.

To verify that Go is correctly installed, open a terminal or command prompt and run the following command:

go version

You should see the version number of Go printed on the screen, indicating that the installation was successful.

Understanding WaitGroups

A WaitGroup is a synchronization primitive provided by the sync package in Go. It is used to wait for a collection of goroutines to finish their execution. The WaitGroup maintains a counter internally, which is incremented every time a new goroutine is spawned and decremented when a goroutine completes its execution.

A WaitGroup has three main methods:

  • Add(delta int): Adds the specified delta to the WaitGroup’s counter. If delta is negative, it subtracts the absolute value of delta from the counter.
  • Done(): Decrements the WaitGroup’s counter by one. Equivalent to calling Add(-1).
  • Wait(): Blocks the current goroutine until the WaitGroup’s counter becomes zero.

By using these methods, we can control the synchronization of goroutines in our Go programs.

Using WaitGroups in Go

Let’s now see how to use WaitGroups in a Go program. We will go through a step-by-step example that demonstrates the usage of WaitGroups to perform parallel image processing.

  1. Start by creating a new Go file named main.go and open it in your favorite text editor.

  2. Import the necessary packages at the top of the file:

     package main
        
     import (
     	"fmt"
     	"sync"
     )
    
  3. Define the main function that will be executed when the program is run:

     func main() {
     	// Create a WaitGroup
     	var wg sync.WaitGroup
        
     	// Add the number of goroutines to the WaitGroup
     	wg.Add(3)
        
     	// Start the goroutines
     	go processImage("image1.jpg", &wg)
     	go processImage("image2.jpg", &wg)
     	go processImage("image3.jpg", &wg)
        
     	// Wait for all the goroutines to finish
     	wg.Wait()
        
     	// All goroutines have completed
     	fmt.Println("All images processed.")
     }
    

    In the code above, we create a WaitGroup wg and add the number of goroutines we want to wait for using the Add method. In this case, we want to wait for 3 goroutines to finish. We then start three goroutines using the go keyword, each calling the processImage function with a different image file name and passing the WaitGroup wg as an argument.

  4. Implement the processImage function that simulates image processing:

     func processImage(filename string, wg *sync.WaitGroup) {
     	// Print a message indicating the start of image processing
     	fmt.Printf("Processing %s...\n", filename)
        
     	// Simulate some image processing
     	// ...
        
     	// Decrement the WaitGroup counter when the goroutine is done
     	wg.Done()
     }
    

    The processImage function takes the file name of the image to be processed and the WaitGroup wg as parameters. Inside the function, we simulate some image processing and then call the Done method on the WaitGroup to decrement its counter, indicating that the goroutine has completed.

  5. Save the file and build/run the program using the following command:

     go run main.go
    

    You should see the messages indicating the start of image processing for each image, followed by the message “All images processed.” when all goroutines have completed.

    Congratulations! You have successfully used WaitGroups to synchronize the execution of multiple goroutines in Go.

Example: Parallel Image Processing

Now that you understand how to use WaitGroups, let’s take it a step further and perform parallel image processing using goroutines and WaitGroups.

  1. In the processImage function, replace the simulated image processing with actual image processing code that loads an image file, applies a filter, and saves the processed image.

  2. Modify the main function to process multiple images in parallel:

     func main() {
     	var wg sync.WaitGroup
        
     	images := []string{"image1.jpg", "image2.jpg", "image3.jpg"}
        
     	for _, img := range images {
     		wg.Add(1)
        
     		go func(filename string) {
     			defer wg.Done()
        
     			processImage(filename)
     		}(img)
     	}
        
     	wg.Wait()
        
     	fmt.Println("All images processed.")
     }
    

    In the modified code above, we create a slice of image file names and iterate over them using a for loop. For each image, we add 1 to the WaitGroup counter and spawn a goroutine that calls an anonymous function. The anonymous function takes the image file name as an argument, defers the call to wg.Done(), and then calls processImage with the file name.

    This way, all the images will be processed in parallel, and the main goroutine will wait for all the goroutines to finish before printing the final message.

  3. Build and run the updated program:

     go run main.go
    

    You should see the image processing messages appearing in a non-deterministic order, indicating that the processing is happening in parallel. Once all the images are processed, the final message “All images processed.” will be printed.

Conclusion

In this tutorial, we learned how to work with WaitGroups in Go to coordinate the execution of multiple goroutines. We explored the usage of WaitGroups through a step-by-step example of parallel image processing. By utilizing WaitGroups effectively, we can achieve efficient and synchronized concurrency in our Go programs.

WaitGroups are just one of the many valuable concurrency primitives provided by the Go language. So, keep exploring and experimenting with Go’s concurrent programming features to unlock its full potential.

Happy coding!