ZetCode

Go WaitGroup

最后修改于 2025 年 5 月 3 日

本文展示了如何在 Golang 中使用 WaitGroup 等待 goroutine 完成。

一个 goroutine 是 Go 中的一个轻量级执行线程,旨在以最小的开销实现并发函数执行。Goroutine 与其他代码并行独立运行,有助于高效的多任务处理,而无需传统线程模型的复杂性。

为了同步多个 goroutine 并确保它们在继续之前完成,Go 提供了 WaitGroup,这是一个在 sync 包中找到的同步机制。WaitGroup 充当一个计数器,跟踪活动 goroutine 的数量,确保执行暂停直到所有 goroutine 完成。

WaitGroup 函数

WaitGroup 提供了三个关键函数来管理并发执行:AddDoneWait

func (wg *WaitGroup) Add(delta int)

Add 函数将 WaitGroup 计数器增加指定的 delta 值,表示要等待的 goroutine 的数量。如果计数器变为零,所有正在等待 Wait 的 goroutine 都将被解除阻塞,允许执行继续。

func (wg *WaitGroup) Done()

Done 函数将 WaitGroup 计数器减一,表示一个 goroutine 已完成其执行。每个 goroutine 完成其任务后都必须调用一次 Done,以确保正确同步。

func (wg *WaitGroup) Wait()

Wait 函数会阻塞执行,直到 WaitGroup 计数器达到零,从而确保所有注册的 goroutine 在继续之前都已完成。这对于协调并行任务和防止依赖代码的过早执行特别有用。

通过利用 WaitGroup,Go 开发者可以有效地管理并发操作,同步执行流程,并在多线程应用程序中避免竞态条件。

运行 goroutines

主程序本身就是一个 goroutine。它可能比它调用的 goroutine 先完成。

main.go
package main

import (
    "fmt"
)

func f1() {
    fmt.Println("goroutine 1")
}

func f2() {
    fmt.Println("goroutine 2")
}

func main() {

    go f1()
    go f2()
}

在该程序中,我们在 main 函数中启动了两个 goroutine。但是,程序在两个 goroutine 完成之前就结束了。

$ go run main.go

该程序不打印任何输出。


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)
}

当我们使用 time.Sleep 睡眠两秒钟时,goroutine 就有时间完成了。

$ go run main.go
goroutine 2
goroutine 1

Go WaitGroup 简单示例

sync.WaitGroup 是一个同步工具,它等待一组 goroutine 完成。

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)
    }
}

我们使用 WaitGroup 同步两个 goroutine 的执行。

var wg sync.WaitGroup
wg.Add(2)

使用 Add 函数,我们告诉它需要等待多少个 goroutine。

go func() {

    count("oranges")
    wg.Done()
}()

我们创建一个匿名的 goroutine。我们使用 Done 告诉 Go 运行时 goroutine 已经完成。

wg.Wait()

Wait 函数会阻塞,直到所有 goroutine 都完成。

time.Sleep(time.Millisecond * 500)

time.Sleep 通常用于在演示程序中减慢 goroutine 的执行速度。

$ go run main.go
counting bananas
counting oranges
counting oranges
counting bananas
counting bananas
counting oranges
counting oranges
counting bananas

我们必须将 WaitGroup 的指针传递给函数。

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()
}

在这个例子中,我们有两个 goroutine。我们将 WaitGroup 的指针传递给它们。

Go WaitGroup 异步 HTTP 请求

在下面的示例中,我们使用 goroutine 发起多个异步 HTTP 请求。

main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

func main() {

    urls := []string{
        "http://webcode.me",
        "https://example.com",
        "http://httpbin.org",
        "https://perl.net.cn",
        "https://pythonlang.cn",
        "https://clojure.net.cn",
    }

    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
}

我们发出多个异步 HTTP 请求。我们发出一个 HEAD 请求并返回响应的状态码。每个请求都包装在一个 goroutine 中。

var wg sync.WaitGroup

WaitGroup 用于等待所有请求完成。

for _, u := range urls {

    wg.Add(1)
    go func(url string) {
        ...
    }(u)
}

我们遍历 url 切片,并向计数器添加一个 goroutine。

go func(url string) {

    defer wg.Done()

    status := doReq(url)
    fmt.Printf("%s - %s\n", url, status)

}(u)

在 goroutine 中,我们发起一个 HEAD 请求,接收响应,并打印状态码。请求完成后,调用 Done 函数来减少计数器。

$ go run main.go
http://webcode.me - 200 OK
https://pythonlang.cn - 200 OK
https://perl.net.cn - 200 OK
https://clojure.net.cn - 200 OK
http://httpbin.org - 200 OK
https://example.com - 200 OK

来源

Go sync 包 - 参考

在本文中,我们使用了 WaitGroup 来等待一组 goroutine 完成。

作者

我叫 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有超过十年的经验。

列出所有 Go 教程