Go WaitGroup
最后修改于 2025 年 5 月 3 日
本文展示了如何在 Golang 中使用 WaitGroup 等待 goroutine 完成。
一个 goroutine 是 Go 中的一个轻量级执行线程,旨在以最小的开销实现并发函数执行。Goroutine 与其他代码并行独立运行,有助于高效的多任务处理,而无需传统线程模型的复杂性。
为了同步多个 goroutine 并确保它们在继续之前完成,Go 提供了 WaitGroup
,这是一个在 sync
包中找到的同步机制。WaitGroup
充当一个计数器,跟踪活动 goroutine 的数量,确保执行暂停直到所有 goroutine 完成。
WaitGroup 函数
WaitGroup
提供了三个关键函数来管理并发执行:Add
、Done
和 Wait
。
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 先完成。
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
该程序不打印任何输出。
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 完成。
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
的指针传递给函数。
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 请求。
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
来源
在本文中,我们使用了 WaitGroup
来等待一组 goroutine 完成。