文章目录
- package main import ( “fmt” “sync” “time” ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 执行完毕,通知 WaitGroup fmt.Printf(“Worker %d startingn”, id) time.Sleep(time.Second) // 模拟耗时操作 fmt.Printf(“Worker %d donen”, id) } func main() { var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) // 启动一个 goroutine 前调用 Add(1) go worker(i, &wg) } wg.Wait() // 阻塞直到所有 Done() 被调用 fmt.Println(“所有任务已完成”) }
- Worker 1 startingWorker 2 startingWorker 3 startingWorker 4 startingWorker 5 startingWorker 2 doneWorker 4 doneWorker 1 doneWorker 3 doneWorker 5 done所有任务已完成 输出顺序可能每次不同,因为 goroutine 是并发执行的。
目录
- 一、什么是 WaitGroup?
- 常用方法:
- 二、为什么需要 WaitGroup?
- 三、实战案例:等待多个协程完成任务
- 示例:开启 5 个 goroutine,每个打印一条消息,主程序等待所有协程完成后再退出。
- 输出示例
- 四、使用建议与注意事项
- ✅ 正确使用方式:
- ⚠️ 易犯错误:
- 五、封装使用:任务调度器
- 六、与其他同步机制对比
- 七、结语
在并发编程中,经常需要在主协程中等待多个子协程执行完毕再进行下一步操作。Go 提供了一个非常实用的同步原语:sync.WaitGroup,可以帮助我们优雅地解决这个问题。
sync.WaitGroup 是 Go 标准库中的一个结构体,用于等待一组 goroutine 执行完成。
| 方法 | 功能说明 |
|---|---|
| Add(n int) | 设置需要等待的 goroutine 数量 |
| Done() | 每个 goroutine 执行完成后调用,计数器减 1 |
| Wait() | 阻塞当前 goroutine,直到计数器归零为止 |
在并发场景中,如果主函数先于子协程执行完毕,程序将会直接退出,导致协程未完成任务。例如:
func main() {
go func() {
fmt.Println("Hello from goroutine")
}()
// 没有等待,主函数直接退出
}
输出往往为空或者输出不完整,根本原因是主 goroutine 没有等待子 goroutine。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 执行完毕,通知 WaitGroup
fmt.Printf("Worker %d startingn", id)
time.Sleep(time.Second) // 模拟耗时操作
fmt.Printf("Worker %d donen", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // 启动一个 goroutine 前调用 Add(1)
go worker(i, &wg)
}
wg.Wait() // 阻塞直到所有 Done() 被调用
fmt.Println("所有任务已完成")
}
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 2 done
Worker 4 done
Worker 1 done
Worker 3 done
Worker 5 done
所有任务已完成
输出顺序可能每次不同,因为 goroutine 是并发执行的。
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 2 done
Worker 4 done
Worker 1 done
Worker 3 done
Worker 5 done
所有任务已完成
输出顺序可能每次不同,因为 goroutine 是并发执行的。
- Add(n) 必须在 启动 goroutine 之前 调用。
- 每个 goroutine 结束时必须调用 Done()。
- Wait() 通常放在主 goroutine 中,阻塞直到所有任务完成。
-
1. 忘记 Done() :
go func() {
fmt.Println("hello")
// 忘记 wg.Done(),会导致 wg.Wait() 永久阻塞
}()
-
2. 在 goroutine 内调用 Add() :
go func() {
wg.Add(1) // ❌不推荐:会出现数据竞争
...
}()
正确方式是主 goroutine 中统一 Add()。
1. 忘记 Done() :
go func() {
fmt.Println("hello")
// 忘记 wg.Done(),会导致 wg.Wait() 永久阻塞
}()
2. 在 goroutine 内调用 Add() :
go func() {
wg.Add(1) // ❌不推荐:会出现数据竞争
...
}()
正确方式是主 goroutine 中统一 Add()。
我们可以封装一个并发调度器,更加灵活:
type TaskScheduler struct {
wg sync.WaitGroup
}
func (t *TaskScheduler) Run(task func()) {
t.wg.Add(1)
go func() {
defer t.wg.Done()
task()
}()
}
func (t *TaskScheduler) Wait() {
t.wg.Wait()
}
使用示例:
func main() {
scheduler := TaskScheduler{}
for i := 0; i < 5; i++ {
index := i
scheduler.Run(func() {
fmt.Printf("Task %d runningn", index)
time.Sleep(500 * time.Millisecond)
})
}
scheduler.Wait()
fmt.Println("All scheduled tasks completed.")
}
| 同步机制 | 用途 |
|---|---|
| sync.WaitGroup | 等待一组协程结束 |
| sync.Mutex | 保护共享资源 |
| sync.Once | 只执行一次的操作(如初始化) |
| channel | 通信为主,也可用于同步控制 |
在并发控制中,sync.WaitGroup 是一种简单却非常强大的工具,能帮助我们控制 goroutine 的生命周期,避免程序提前退出或资源泄漏等问题。掌握其使用方法,是每位 Go 开发者的必备技能。
✅ 实战建议:在进行网络抓取、任务分发、并发计算等场景时,推荐优先考虑 WaitGroup 来管理协程。
到此这篇关于Go语言使用WaitGroup等待多个协程完成的文章就介绍到这了,更多相关Go WaitGroup等待多协程完成内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!
您可能感兴趣的文章:
- go sync包中的互斥锁Mutex和等待组WaitGroup使用详解
- Go语言同步等待组sync.WaitGroup结构体对象方法详解
- 在golang中使用Sync.WaitGroup解决等待的问题