In concurrent programming, handling synchronization is crucial to ensure smooth operation and correct results. In Go, sync.WaitGroup is a powerful synchronization tool to manage the execution of goroutines and wait for their completion before proceeding.
Understanding sync.WaitGroup
The sync.WaitGroup is part of Go's sync package. It provides a simple mechanism to wait for a collection of goroutines to finish without requiring complex logic or imposing performance overhead.
Key Methods of sync.WaitGroup
Add(int): Increment the wait group counter by the specified value.Done(): Decrement the wait group counter by one. Typically called as a goroutine completes its task.Wait(): Block execution until the counter becomes zero.
Using sync.WaitGroup: A Basic Example
Let's look at a basic example where we use sync.WaitGroup to manage three concurrent goroutines:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Notify the WaitGroup that this worker is done
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // Simulate work with sleep
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ { // Start 3 workers
wg.Add(1) // Increment the counter for each worker
go worker(i, &wg)
}
wg.Wait() // Wait for all workers to complete
fmt.Println("All workers completed.")
}
In the example above, we created three goroutines identified as worker 1, worker 2, and worker 3. The WaitGroup was used to track their completion and ensure that "All workers completed." message is printed only after all goroutines have finished.
Advanced Example: Error Handling and Synchronization
Sometimes, after launching goroutines, you may also want to handle errors returned by these goroutines. Here's a pattern you can use:
package main
import (
"fmt"
"sync"
"errors"
)
func workerWithErr(id int, wg *sync.WaitGroup, errs chan<- error) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// Simulate some work with a potential error outcome
if id == 2 { // Example condition where a worker might fail
errs <- errors.New(fmt.Sprintf("Worker %d encountered an error", id))
}
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
errs := make(chan error, 3) // Capacity set to the number of goroutines
for i := 1; i <= 3; i++ {
wg.Add(1)
go workerWithErr(i, &wg, errs)
}
wg.Wait() // Ensure all workers finish
close(errs) // Close the error channel
for err := range errs {
if err != nil {
fmt.Println(err)
}
}
fmt.Println("All workers completed with error handling.")
}
In this example, the worker function might potentially return an error, which is pushed onto an error channel. The main function handles these potential errors after confirming all goroutines have finished.
This method does not halt execution mid-stream due to an error but awaits all tasks' completion, potentially valuable in scenarios immune to partial failures.
Conclusion
The sync.WaitGroup is among Go's most efficient tools for task synchronization. By utilizing sync.WaitGroup, you can systematically execute parallel computing processes and effectively incorporate error management patterns where applicable.