WaitGroup
In concurrent programs, there are situations where you need to wait for the results of multiple independently running processes. In the context of Go, this often means waiting for multiple goroutines to finish. For a single process, you can simply use a "done" channel, such as context.Done
. However, the sync.WaitGroup
provides an excellent solution for coordinating numerous goroutines.
Essentially, sync.WaitGroup
is a concurrent safe counter that you can use to track how many goroutines have been created and how many have completed.
WaitGroup
has three important methods. The Add
method increments the counter by an integer, Done
decrements the counter by one, and Wait
blocks the calling thread until the WaitGroup
counter reaches zero.
Here's a simple example where an anonymous function is executed within a goroutine, and the main thread waits for its completion using WaitGroup
. The wg
variable is incremented by calling the Add
function, which is then decremented by one using Done
, it gets called, when the goroutine completes.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
fmt.Println("Hello Follow The Pattern")
time.Sleep(2 * time.Second)
wg.Done()
}()
wg.Wait()
fmt.Println("Program finished!")
}
You can run the above example here (opens in a new tab).
The previous simple example can also be solved using channels, but the following code demonstrates a scenario, which a WaitGroup
can solve easier, where numerous goroutines need to be coordinated simultaneously. You need to ensure proper maintenance of the WaitGroup
counter by calling Done
at the right place.
package main
import (
"fmt"
"sync"
)
func main() {
var wait sync.WaitGroup
numberOfRoutines := 5
wait.Add(numberOfRoutines)
for i := 0; i < numberOfRoutines; i++ {
go func(ID int) {
fmt.Printf("ID:%d: Hello FP routine!\n", ID)
wait.Done()
}(i)
}
wait.Wait()
}
You can run the above example here (opens in a new tab).
The ID
values may not necessarily be printed in ascending order because the invocation of goroutines does not necessarily happen in the same order as the command to create the goroutine.
In the following code snippet, the Wait
method is not called, so it won't block the main goroutine, which may result unfinished processes.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
fmt.Println("Hello Follow The Pattern")
time.Sleep(2 * time.Second)
wg.Done()
}()
// wg.Wait() won't block the main thread
fmt.Println("Program finished!")
}
You can run the above example here (opens in a new tab).
The following program will result in an error message: "all goroutines are asleep - deadlock!" because the Done
method is not called within the anonymous function, and the Wait
method waits indefinitely until the counter reaches zero.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
fmt.Println("Hello Follow The Pattern")
time.Sleep(2 * time.Second)
// wg.Done()
}()
wg.Wait() // deadlock!
fmt.Println("Program finished!")
}
You can run the above example here (opens in a new tab).