Go WaitGroup
last modified May 3, 2025
In this article we show how to wait for goroutines to finish in Golang using WaitGroup.
A goroutine is a lightweight execution thread in Go, designed to enable concurrent function execution with minimal overhead. Goroutines run independently alongside other code, facilitating efficient multitasking without the complexity of traditional threading models.
To synchronize multiple goroutines and ensure they complete before proceeding, Go provides the WaitGroup
, a synchronization mechanism found in the sync
package. A WaitGroup
acts as a counter that tracks the number of active goroutines, ensuring that execution pauses until all goroutines finish.
WaitGroup Functions
The WaitGroup
provides three key functions for managing concurrent
execution: Add
, Done
, and Wait
.
func (wg *WaitGroup) Add(delta int)
The Add
function increments the WaitGroup counter by the specified
delta
value, representing the number of goroutines to wait for. If
the counter becomes zero, all goroutines that were waiting on Wait
are unblocked, allowing execution to resume.
func (wg *WaitGroup) Done()
The Done
function decrements the WaitGroup
counter by
one, indicating that a goroutine has finished its execution. Every goroutine
must call Done
once it completes its task to ensure proper
synchronization.
func (wg *WaitGroup) Wait()
The Wait
function blocks execution until the WaitGroup
counter reaches zero, ensuring that all registered goroutines finish before
proceeding. This is particularly useful for coordinating parallel tasks and
preventing premature execution of dependent code.
By leveraging WaitGroup
, Go developers can efficiently manage
concurrent operations, synchronize execution flow, and avoid race conditions in
multi-threaded applications.
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.