ZetCode

Go WaitGroup

last modified August 24, 2023

In this article we show how to wait for goroutines to finish in Golang using WaitGroup.

$ go version
go version go1.18.1 linux/amd64

We use Go version 1.18.

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.

main.go
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.


main.go
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.

main.go
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.

main.go
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.

main.go
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

In this article we have used the WaitGroup to wait for a collection of goroutines to finish.

Author

My name is Jan Bodnar and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Go tutorials.