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.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
}
我们发出多个异步 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.org - 200 OK http://httpbin.org - 200 OK https://example.com - 200 OK
来源
在本文中,我们使用了 WaitGroup 来等待一组 goroutine 完成。