A WaitGroup is a synchronization primitive provided by Go’s sync
package, which allows you to wait for a collection of goroutines to finish executing. It is useful when you launch multiple goroutines and need to block the main function (or any other goroutine) until all of them have completed their tasks.
- Counter-based: A WaitGroup maintains a counter that represents the number of goroutines it is waiting for.
- Blocking: It allows one goroutine to block until all the goroutines in the group have finished executing.
- Thread-safe: You can safely use a WaitGroup across multiple goroutines.
To use a WaitGroup, you follow these basic steps:
- Add the number of goroutines that you want to wait for using
Add()
. - Launch goroutines as usual, but make sure to call
Done()
within each goroutine when it finishes. - Wait for the goroutines to complete using
Wait()
.
package main
import (
"fmt"
"sync"
)
func printMessage(message string, wg *sync.WaitGroup) {
defer wg.Done() // Decrement the counter when the goroutine finishes
fmt.Println(message)
}
func main() {
var wg sync.WaitGroup
wg.Add(2) // We are going to wait for 2 goroutines
go printMessage("Hello from goroutine 1!", &wg)
go printMessage("Hello from goroutine 2!", &wg)
wg.Wait() // Block until the counter becomes 0
fmt.Println("All goroutines finished!")
}
- We use
wg.Add(2)
to indicate that we are waiting for two goroutines. - Each goroutine calls
wg.Done()
when it completes. - The
main
function blocks atwg.Wait()
until both goroutines have finished.
- Add: Before starting the goroutines, you call
Add(n)
to set the number of goroutines you expect to wait for. - Done: Each goroutine must call
Done()
when it finishes its task. This decreases the counter by 1. - Wait: The
Wait()
function blocks the calling goroutine until the counter reaches zero, which means all goroutines have finished.
- Defer Done: It’s common practice to use
defer wg.Done()
in each goroutine to ensure that it is called when the goroutine completes, even if an error occurs. - Thread-safety: You can safely modify the WaitGroup counter across different goroutines, but you should always call
Done()
after the task completes.
WaitGroups are commonly used in situations where:
- You launch multiple goroutines to handle concurrent tasks and need to wait for all of them to finish.
- You are running multiple parallel operations (e.g., web requests, file operations, or data processing) and want to ensure that all tasks complete before moving forward.
- Avoid race conditions: Make sure the counter is updated correctly in concurrent scenarios to avoid any issues.
- Use Defer for
Done()
: Always deferwg.Done()
to ensure it is called even if the goroutine exits unexpectedly (e.g., due to a panic). - Don’t call
Add()
while waiting: Avoid callingAdd()
after you’ve started waiting withWait()
—this can lead to race conditions.
After mastering WaitGroups, the next step is learning how to handle mutexes to manage data access between goroutines. You can also explore channels for inter-goroutine communication.