首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | Mutex:如何解决资源并发访问问题?
02 | Mutex:庖丁解牛看实现
03|Mutex:4种易错场景大盘点
04| Mutex:骇客编程,如何拓展额外功能?
05| RWMutex:读写锁的实现原理及避坑指南
06 | WaitGroup:协同等待,任务编排利器
07 | Cond:条件变量的实现机制及避坑指南
08 | Once:一个简约而不简单的并发原语
09 | map:如何实现线程安全的map类型?
10 | Pool:性能提升大杀器
11 | Context:信息穿透上下文
12 | atomic:要保证原子操作,一定要使用这几种方法
13 | Channel:另辟蹊径,解决并发问题
14 | Channel:透过代码看典型的应用模式
15 | 内存模型:Go如何保证并发读写的顺序?
16 | Semaphore:一篇文章搞懂信号量
17 | SingleFlight 和 CyclicBarrier:请求合并和循环栅栏该怎么用?
18 | 分组操作:处理一组子任务,该用什么并发原语?
19 | 在分布式环境中,Leader选举、互斥锁和读写锁该如何实现?
20 | 在分布式环境中,队列、栅栏和STM该如何实现?
当前位置:
首页>>
技术小册>>
Golang并发编程实战
小册名称:Golang并发编程实战
### 06 | WaitGroup:协同等待,任务编排利器 在并发编程的世界里,高效地管理和协调多个goroutine的执行是至关重要的。Go语言为此提供了多种同步机制,其中`sync.WaitGroup`是处理并发任务等待和同步的一个非常实用的工具。它允许你等待一组goroutine的完成,从而在进行下一步操作之前确保所有相关任务都已成功执行完毕。本章将深入探讨`WaitGroup`的工作原理、使用方法以及它在复杂并发任务编排中的应用。 #### 一、WaitGroup简介 `sync.WaitGroup`是Go标准库`sync`包下的一个结构体,用于等待一组goroutines的完成。它提供了三个方法:`Add(delta int)`、`Done()`和`Wait()`,通过这些方法,开发者可以方便地管理并发任务的生命周期。 - **Add(delta int)**:增加或减少WaitGroup的计数器值。当你想启动一个新的goroutine时,通常会调用`Add(1)`来增加计数器的值,以表示有一个新的任务被加入到等待组中。如果`delta`是负数,则表示任务已经完成,计数器相应减少。 - **Done()**:是`Add(-1)`的便捷封装,用于在goroutine结束时调用,表示该任务已完成。 - **Wait()**:阻塞当前goroutine,直到WaitGroup的计数器归零。这意味着所有通过`Add`方法添加的任务都已经通过`Done`方法标记为完成。 #### 二、WaitGroup的基本使用 `WaitGroup`的使用场景非常广泛,下面通过一个简单的例子来说明其基本用法。 ```go package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // 确保goroutine结束时调用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 <= 5; i++ { wg.Add(1) // 增加计数器 go worker(i, &wg) // 启动goroutine } wg.Wait() // 等待所有worker完成 fmt.Println("All workers have finished") } ``` 在这个例子中,我们创建了五个worker goroutine,每个goroutine都执行一项模拟的耗时任务。通过`WaitGroup`,我们能够在所有worker都完成任务之前阻塞`main`函数,从而确保程序以有序的方式结束。 #### 三、WaitGroup的高级应用 `WaitGroup`不仅限于简单的并发任务等待,它还可以与Go的其他并发特性结合使用,实现更复杂的任务编排和同步逻辑。 ##### 1. 嵌套WaitGroup 有时,一个大的并发任务可以被分解为多个子任务,每个子任务又包含多个更小的并发任务。这时,可以使用嵌套的`WaitGroup`来管理这些复杂的层次结构。 ```go func main() { var wg, subWg sync.WaitGroup wg.Add(2) // 假设有两个主要任务 go func() { defer wg.Done() subWg.Add(2) // 第一个主要任务包含两个子任务 go subTask(&subWg, "A1") go subTask(&subWg, "A2") subWg.Wait() // 等待第一个主要任务的所有子任务完成 }() go func() { defer wg.Done() subWg.Add(1) // 第二个主要任务包含一个子任务 go subTask(&subWg, "B1") subWg.Wait() // 等待第二个主要任务的子任务完成 }() wg.Wait() // 等待所有主要任务完成 } func subTask(wg *sync.WaitGroup, name string) { defer wg.Done() // 执行子任务 fmt.Printf("Subtask %s is running\n", name) time.Sleep(time.Second) fmt.Printf("Subtask %s done\n", name) } ``` 在上面的代码中,我们展示了如何使用嵌套的`WaitGroup`来管理具有层次结构的并发任务。每个主要任务都维护一个自己的`WaitGroup`实例(`subWg`),用于等待其内部子任务的完成。而外部的`WaitGroup`(`wg`)则用于等待所有主要任务的完成。 ##### 2. 动态增减计数器 在某些场景下,我们可能需要根据运行时的情况动态地增减`WaitGroup`的计数器。虽然这听起来有些复杂,但通过合理的逻辑设计,它仍然是可以实现的。 例如,我们可能需要根据从网络或其他并发源接收到的消息数量来动态地启动和等待goroutine的完成。这时,我们可以在接收到每个新消息时调用`Add(1)`,并在对应的goroutine结束时调用`Done()`。 #### 四、注意事项 - **避免重复Add或Done**:确保每个`Add`调用都有对应的`Done`调用,且计数器的增减逻辑是正确的,否则可能导致死锁或程序行为异常。 - **WaitGroup与错误处理**:`WaitGroup`本身不提供错误处理机制。如果goroutine在执行过程中遇到错误,你需要通过其他方式(如channel、context等)来传播和处理这些错误。 - **避免在WaitGroup中创建闭包循环引用**:当使用`WaitGroup`与闭包时,要特别注意闭包可能导致的循环引用问题,这可能会阻止垃圾收集器回收不再使用的资源。 #### 五、总结 `sync.WaitGroup`是Go语言并发编程中一个非常实用的工具,它提供了一种简单而高效的方式来等待一组goroutine的完成。通过合理利用`WaitGroup`,我们可以编写出结构清晰、易于维护的并发代码。无论是简单的并发任务等待,还是复杂的任务编排和同步,`WaitGroup`都能提供强大的支持。然而,正如所有强大的工具一样,正确使用`WaitGroup`也需要我们对其工作原理和限制有深入的理解。希望本章的内容能够帮助你更好地掌握`WaitGroup`,从而在并发编程的道路上走得更远。
上一篇:
05| RWMutex:读写锁的实现原理及避坑指南
下一篇:
07 | Cond:条件变量的实现机制及避坑指南
该分类下的相关小册推荐:
深入浅出Go语言核心编程(六)
Golang修炼指南
GO面试指南
go编程权威指南(三)
Go开发基础入门
深入浅出Go语言核心编程(八)
深入解析go语言
Go Web编程(下)
Go语言入门实战经典
Go开发权威指南(上)
深入浅出Go语言核心编程(七)
深入浅出Go语言核心编程(四)