Advanced Golang
Concurrency
WaitGroups
In this lesson, we explore WaitGroups in Go, a key tool for managing concurrency. In Go programs, the main goroutine can sometimes terminate before spawned goroutines have begun or finished execution. WaitGroups provide an elegant solution to synchronize multiple goroutines without resorting to arbitrary timeouts.
Declaring and Using a WaitGroup
To get started, declare a WaitGroup just like any other variable using the var
keyword. For example:
var wg sync.WaitGroup
Make sure to import the sync
package at the beginning of your file:
import "sync"
A WaitGroup offers three primary methods to control goroutine execution:
Add
Use this method to set the number of goroutines that the WaitGroup should wait for. It increases the internal counter by the specified number.wg.Add(n) // n is the number of goroutines to wait for
Wait
This method blocks the execution of the code until the WaitGroup's internal counter reaches zero. Typically, you call it after all desired goroutines have been launched.wg.Wait()
Done
Each goroutine should callwg.Done()
when its work is completed. This method decreases the WaitGroup's counter by one.wg.Done()
Note
Ensure that every call to wg.Add(n)
eventually corresponds to n
calls to wg.Done()
. Failing to do so will result in your program hanging indefinitely.
Understanding WaitGroups with an Example
Consider a scenario where you need to manage three concurrent tasks. Initially, the WaitGroup's counter is zero. By invoking wg.Add(3)
, the counter is increased to three. Each of the three goroutines will call wg.Done()
upon task completion, decrementing the counter by one. Meanwhile, wg.Wait()
blocks further execution until all goroutines complete and the counter reaches zero.
Below is a consolidated example that demonstrates the use of WaitGroups:
package main
import (
"fmt"
"sync"
"time"
)
// worker simulates a task performed by a goroutine.
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
// Simulate some work with a delay.
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
numWorkers := 3
// Increment the WaitGroup counter by the number of goroutines.
wg.Add(numWorkers)
// Launch the workers.
for i := 1; i <= numWorkers; i++ {
go worker(i, &wg)
}
// Block main until all goroutines finish.
wg.Wait()
fmt.Println("All workers complete")
}
In this example:
- The main function sets the WaitGroup counter to 3 using
wg.Add(3)
. - Three goroutines are spawned, each executing the
worker
function. - Each worker calls
wg.Done()
after finishing its task. - The main goroutine waits on
wg.Wait()
, ensuring that it only proceeds after every worker has completed execution.
Performance Consideration
Using WaitGroups correctly helps to avoid potential issues with premature termination of your main routine, maintaining the reliability and predictability of concurrent execution.
That concludes our lesson on WaitGroups in Go. For further learning, you may explore more advanced concurrency patterns and synchronization techniques in Go.
Watch Video
Watch video content