waitgroup的使用
在 Go 语言中,sync.WaitGroup
是用于等待一组 Goroutine 完成执行的同步工具,常用于主 Goroutine 协调多个子 Goroutine 的生命周期。以下是其核心用法及注意事项:
1. 基本用法
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 确保任务完成时调用 Done
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // 模拟耗时任务
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // 每启动一个 Goroutine,计数器 +1
go worker(i, &wg) // 传递 WaitGroup 的指针
}
wg.Wait() // 阻塞,直到所有 Goroutine 完成
fmt.Println("All workers completed")
}
输出:
Worker 3 starting
Worker 1 starting
Worker 2 starting
Worker 1 done
Worker 2 done
Worker 3 done
All workers completed
2. 核心方法
方法 | 作用 |
---|---|
wg.Add(n int) |
增加等待的 Goroutine 数量(通常在启动 Goroutine 前调用) |
wg.Done() |
标记一个 Goroutine 完成(等价于 wg.Add(-1) ,建议用 defer 调用) |
wg.Wait() |
阻塞主 Goroutine,直到所有子 Goroutine 完成(计数器归零) |
3. 注意事项
(1) 正确传递指针
-
错误示例(值传递导致死锁):
go worker(i, wg) // 值传递,Done() 不影响原 WaitGroup
-
正确方式:
go worker(i, &wg) // 传递指针
(2) 确保 Add 和 Done 匹配
-
错误示例(Add 次数不足):
wg.Add(1) for i := 0; i < 3; i++ { go func() { wg.Done() }() // 触发 panic:计数器为负 }
-
正确方式:
wg.Add(3) // 明确指定需要等待 3 次 Done for i := 0; i < 3; i++ { go func() { wg.Done() }() }
(3) 使用 defer 调用 Done
避免因 Goroutine 提前退出或 panic 导致 Done()
未执行:
func worker(wg *sync.WaitGroup) {
defer wg.Done() // 确保 Done 一定执行
// ...(可能 panic 的代码)
}
4. 常见场景
(1) 批量任务等待
var wg sync.WaitGroup
urls := []string{"https://example.com", "https://google.com"}
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
resp, _ := http.Get(u)
defer resp.Body.Close()
}(url)
}
wg.Wait()
fmt.Println("All HTTP requests completed")
(2) 结合错误处理
使用通道收集子 Goroutine 的错误:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
errCh := make(chan error, 3)
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
if err := doTask(id); err != nil {
errCh <- err
}
}(i)
}
go func() {
wg.Wait()
close(errCh) // 关闭通道,避免主 Goroutine 死锁
}()
for err := range errCh {
fmt.Println("Error:", err) // Error: 0 is not equal to 2 // Error: 2 is not equal to 2
}
}
func doTask(i int) error {
if i != 1 {
return fmt.Errorf("%d is not equal to 2", i)
}
return nil
}
5. 死锁陷阱
未调用 Wait
wg.Add(1)
go func() { wg.Done() }()
// 缺少 wg.Wait(),主 Goroutine 直接退出
Done 调用次数超过 Add
wg.Add(1)
go func() {
wg.Done()
wg.Done() // panic: negative WaitGroup counter
}()
6. errgroup
鉴于使用 sync.WaitGroup 容易出错,Go 官方包 errgroup 对 sync.WaitGroup 做了进一步封装,不再需要 Add 和 Done,用起来更加方便。详见 golang.org/x/sync/errgroup。
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group is valid and does not cancel on error.
type Group struct {
cancel func()
wg sync.WaitGroup
errOnce sync.Once
err error
}
使用示例:
package main
import (
"fmt"
"golang.org/x/sync/errgroup"
)
func foo1() error {
fmt.Println("exit foo1")
return nil
}
func foo2() error {
fmt.Println("exit foo2")
return nil
}
func main() {
fmt.Println("enter main")
var g errgroup.Group
g.Go(foo1)
g.Go(foo2)
fmt.Println("g.Wait()")
g.Wait()
fmt.Println("exit main")
}
运行输出:
enter main
wg.Wait()
exit foo2
exit foo1
exit main
总结
- 何时使用:需要等待多个 Goroutine 全部完成后继续执行主逻辑。
- 最佳实践:
- 始终通过指针传递
WaitGroup
。 - 使用
defer wg.Done()
确保资源释放。 - 在启动 Goroutine 前调用
Add
,避免竞态条件。
- 始终通过指针传递
- 替代方案:简单场景可用
channel
或errgroup.Group
(支持错误传播)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!