答案:sync.WaitGroup用于等待一组Goroutine完成任务,通过Add()增加计数、Done()减少计数、Wait()阻塞直至计数归零,解决主Goroutine过早退出和任务同步问题,常与channel和Mutex配合使用,需注意Add/Done调用时机、避免闭包陷阱并结合defer使用。

在Go语言中,
sync.WaitGroup
sync.WaitGroup
Add()
Done()
Wait()
Add(delta int)
WaitGroup
WaitGroup
wg.Add(N)
wg.Add(1)
Done()
WaitGroup
wg.Done()
WaitGroup
defer wg.Done()
Wait()
WaitGroup
Add()
Done()
下面是一个基础的实践示例:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 确保在函数退出时通知 WaitGroup
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Duration(id) * time.Second) // 模拟工作
fmt.Printf("Worker %d finished\n", id)
}
func main() {
var wg sync.WaitGroup
numWorkers := 3
fmt.Println("Main: Starting workers...")
for i := 1; i <= numWorkers; i++ {
wg.Add(1) // 每启动一个 worker,计数器加 1
go worker(i, &wg)
}
fmt.Println("Main: Waiting for workers to complete...")
wg.Wait() // 阻塞主 Goroutine,直到所有 worker 都完成
fmt.Println("Main: All workers completed. Exiting.")
}
运行上述代码,你会看到主 Goroutine 会等待所有
worker
WaitGroup
立即学习“go语言免费学习笔记(深入)”;
在我看来,Go 并发编程最让人头疼的不是如何启动多个 Goroutine,而是如何知道它们何时结束,以及如何优雅地协调这些任务。设想一下,你启动了十几个 Goroutine 去处理数据、发送请求,而你的主程序却一头雾水,不知道这些“小弟”们干得怎么样了,甚至可能在它们完成之前就直接退出了。这不就乱套了吗?
WaitGroup
WaitGroup
WaitGroup
Wait()
WaitGroup
channel
WaitGroup
channel
它本质上是一个“计数器”,
Add()
Done()
Wait()
Go 的并发工具箱里宝贝不少,
WaitGroup
channel
Mutex
sync.WaitGroup
WaitGroup
chan
channel
channel
channel
WaitGroup
channel
channel
sync.Mutex
Mutex
Mutex
Mutex
Lock()
Unlock()
WaitGroup
总结来说:
WaitGroup
channel
Mutex
它们是互补而非互斥的。在复杂的并发场景中,我们经常会看到它们协同工作。比如,你可能会用
WaitGroup
channel
Mutex
实践出真知,但实践中也容易踩坑。
WaitGroup
WaitGroup
常见的陷阱:
Add()
wg.Add(1)
wg.Wait()
wg.Add(1)
wg.Wait()
Add(1)
Wait()
Add(1)
Done()
WaitGroup
// wg.Add(1) 在 go func() 之后,可能导致问题
// for i := 0; i < 5; i++ {
// go func() {
// defer wg.Done()
// fmt.Println("Worker done")
// }()
// wg.Add(1) // 错误!
// }忘记 defer wg.Done()
wg.Done()
WaitGroup
wg.Wait()
// func worker(wg *sync.WaitGroup) {
// // 如果这里发生 panic,wg.Done() 将不会被调用
// // wg.Done()
// }对 WaitGroup
wg.Done()
wg.Add()
panic
Done()
Add()
Goroutine 闭包陷阱:
// for i := 0; i < 5; i++ {
// wg.Add(1)
// go func() {
// defer wg.Done()
// fmt.Printf("Worker %d\n", i) // 这里的 i 最终会是 4 或 5
// }()
// }最佳实践:
wg.Add()
WaitGroup
// 正确的做法
for i := 0; i < numWorkers; i++ {
wg.Add(1) // 先增加计数
go worker(i, &wg) // 再启动 Goroutine
}或者,如果 Goroutine 数量是固定的,可以直接
wg.Add(numWorkers)
始终使用 defer wg.Done()
defer wg.Done()
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 确保在函数退出时通知 WaitGroup
// ... 业务逻辑 ...
}处理 Goroutine 闭包陷阱:将循环变量作为参数传递给 Goroutine 函数,或者在循环内部创建一个局部变量来捕获当前迭代的值。
// 正确的做法:将 i 作为参数传递
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) { // id 是一个新的局部变量
defer wg.Done()
fmt.Printf("Worker %d\n", id)
}(i) // 将 i 的当前值传递给 Goroutine
}
// 或者在循环内部创建新变量
for i := 0; i < 5; i++ {
wg.Add(1)
taskID := i // 创建一个当前 i 值的副本
go func() {
defer wg.Done()
fmt.Printf("Worker %d\n", taskID)
}()
}错误处理和上下文(Context)结合使用:
WaitGroup
channel
context.Context
channel
wg.Wait()
channel
遵循这些最佳实践,可以大大减少在使用
WaitGroup
以上就是Golang使用WaitGroup等待多任务完成实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号