Go WaitGroup
last modified April 11, 2024
In this article we show how to wait for goroutines to finish in Golang using WaitGroup.
$ go version go version go1.22.2 linux/amd64
We use Go version 1.22.2.
Goroutine is a lightweight execution thread. It is a function that runs concurrently alongside other running code.
A WaitGroup
waits for a collection of goroutines to finish. It acts
as a counter holding the number of functions or goroutines to wait for.
WaitGroup
is part of the sync package.
WaitGroup functions
The WaitGroup
has three functions: Add
,
Done
, and Wait
.
func (wg *WaitGroup) Add(delta int)
The Add
function adds a delta value to the WaitGroup counter. If
the counter becomes zero, all goroutines blocked on Wait
are
released.
func (wg *WaitGroup) Done()
The Done
decrements the WaitGroup
counter by one. It
is called when the goroutine finishes execution.
func (wg *WaitGroup) Wait()
The Wait
blocks until the WaitGroup
counter is zero.
Running goroutines
The main program is itself a goroutine. It may finish earlier than the goroutines it has invoked.
package main import ( "fmt" ) func f1() { fmt.Println("goroutine 1") } func f2() { fmt.Println("goroutine 2") } func main() { go f1() go f2() }
In the program, we launch two goroutines in the main function. However, the program finishes before the two goroutines.
$ go run main.go
The program prints no output.
package main import ( "fmt" "time" ) func f1() { fmt.Println("goroutine 1") } func f2() { fmt.Println("goroutine 2") } func main() { go f1() go f2() time.Sleep(2 * time.Second) }
When we sleep for two seconds with time.Sleep
, the goroutines have
time to finish.
$ go run main.go goroutine 2 goroutine 1
Go WaitGroup simple example
The sync.WaitGroup
is a synchronization tool which waits for a
collection of goroutines to finish.
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup wg.Add(2) go func() { count("oranges") wg.Done() }() go func() { count("bananas") wg.Done() }() wg.Wait() } func count(thing string) { for i := 0; i < 4; i++ { fmt.Printf("counting %s\n", thing) time.Sleep(time.Millisecond * 500) } }
We synchronize the execution of two goroutines with WaitGroup
.
var wg sync.WaitGroup wg.Add(2)
With the Add
function, we tell how may goroutines we wait for.
go func() { count("oranges") wg.Done() }()
We create an anonymous goroutine. We tell the Go runtime that the goroutine has
finished with Done
.
wg.Wait()
The Wait
function blocks until all goroutines have finished.
time.Sleep(time.Millisecond * 500)
The time.Sleep
is often used to slow down the execution of
goroutines in demonstration programs.
$ go run main.go counting bananas counting oranges counting oranges counting bananas counting bananas counting oranges counting oranges counting bananas
We must pass a pointer to a WaitGroup
in a function.
package main import ( "fmt" "sync" ) func f1(wg *sync.WaitGroup) { defer wg.Done() fmt.Println("goroutine 1") } func f2(wg *sync.WaitGroup) { defer wg.Done() fmt.Println("goroutine 2") } func main() { var wg sync.WaitGroup wg.Add(1) go f1(&wg) wg.Add(1) go f2(&wg) wg.Wait() }
In the example, we have two goroutines. We pass them a pointer to the
WaitGroup
.
Go WaitGroup async HTTP requests
In the following example we use goroutines to make multiple asynchronous HTTP requests.
package main import ( "fmt" "log" "net/http" "sync" ) func main() { urls := []string{ "http://webcode.me", "https://example.com", "http://httpbin.org", "https://www.perl.org", "https://www.python.org", "https://clojure.org", } var wg sync.WaitGroup for _, u := range urls { wg.Add(1) go func(url string) { defer wg.Done() status := doReq(url) fmt.Printf("%s - %s\n", url, status) }(u) } wg.Wait() } func doReq(url string) (status string) { resp, err := http.Head(url) if err != nil { log.Println(err) return } return resp.Status }
We make multiple asynchronous HTTP requests. We make a HEAD request and return the status code of the response. Each request is wrapped inside one goroutine.
var wg sync.WaitGroup
The WaitGroup
is used to wait for all requests to finish.
for _, u := range urls { wg.Add(1) go func(url string) { ... }(u) }
We go over the slice of the urls and add one goroutine to the counter.
go func(url string) { defer wg.Done() status := doReq(url) fmt.Printf("%s - %s\n", url, status) }(u)
Withing the goroutine, we generate a HEAD request, receive the response, and
print the status code. After the request has finished, the Done
function is called to decrease the counter.
$ go run main.go http://webcode.me - 200 OK https://www.python.org - 200 OK https://www.perl.org - 200 OK https://clojure.org - 200 OK http://httpbin.org - 200 OK https://example.com - 200 OK
Source
In this article we have used the WaitGroup
to wait for a
collection of goroutines to finish.
Author
List all Go tutorials.