等待任务井然有序:Go WaitGroup 并发编程攻略
2022-11-11 02:40:09
并发编程的利与弊
在单线程编程中,任务只能一个接一个地执行,当任务数量繁多时,程序执行速度会变得十分缓慢。并发编程应运而生,它允许程序同时执行多个任务,大幅提高程序效率。
然而,并发编程也带来了一些新的挑战,其中之一便是任务同步。当多个任务同时执行时,需要一种机制确保它们在适当的时候以正确的顺序执行,WaitGroup 就是为解决这一问题而诞生的。
WaitGroup 简介
WaitGroup 是 Go 语言内置的一个并发原语,它可以帮助开发者轻松实现任务同步和等待。WaitGroup 的基本原理很简单:它维护着一个计数器,表示需要等待的任务数量。当某个任务完成时,它会调用 WaitGroup 的 Done 方法,将计数器减一。当计数器减至 0 时,WaitGroup 的 Wait 方法会返回,表示所有任务都已完成。
WaitGroup 的基本用法
WaitGroup 的基本用法非常简单。首先,创建一个 WaitGroup 对象,然后使用 WaitGroup 的 Add 方法增加计数器,表示需要等待的任务数量。当某个任务完成时,使用 WaitGroup 的 Done 方法将计数器减一。最后,使用 WaitGroup 的 Wait 方法等待所有任务完成。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// 添加需要等待的任务数量
wg.Add(2)
// 创建两个协程,每个协程执行一个任务
go func() {
// 模拟任务执行
fmt.Println("任务 1 完成")
wg.Done() // 任务完成后,将计数器减一
}()
go func() {
// 模拟任务执行
fmt.Println("任务 2 完成")
wg.Done() // 任务完成后,将计数器减一
}()
// 等待所有任务完成
wg.Wait()
fmt.Println("所有任务完成")
}
WaitGroup 的源码剖析
WaitGroup 的源码非常简洁,只有几十行代码。WaitGroup 的核心数据结构是一个原子整型变量,表示需要等待的任务数量。WaitGroup 的 Add 、Done 和 Wait 方法都是原子操作,不会被其他协程中断。
type WaitGroup struct {
state int32
}
func (wg *WaitGroup) Add(delta int) {
atomic.AddInt32(&wg.state, int32(delta))
}
func (wg *WaitGroup) Done() {
atomic.AddInt32(&wg.state, -1)
}
func (wg *WaitGroup) Wait() {
for atomic.LoadInt32(&wg.state) != 0 {
runtime.Gosched()
}
}
WaitGroup 的易错盘点
WaitGroup 是一个非常简单的并发原语,但它也有一些容易出错的地方,以下是一些常见的错误:
- 忘记调用 WaitGroup 的 Done 方法。这会导致 WaitGroup 的计数器永远不会减至 0,导致程序永远不会结束。
- 在 WaitGroup 的 Wait 方法返回之前调用 WaitGroup 的 Add 方法。这会导致 WaitGroup 的计数器增加,导致程序永远不会结束。
- 在 WaitGroup 的 Wait 方法返回之前调用 WaitGroup 的 Done 方法多次。这会导致 WaitGroup 的计数器减小到负数,导致程序出现异常。
总结
WaitGroup 是一个非常有用的并发原语,它可以帮助开发者轻松实现任务同步和等待。在 Go 并发编程中,WaitGroup 是一个必不可少的工具。
常见问题解答
-
WaitGroup 如何确保任务以正确的顺序执行?
WaitGroup 不会确保任务以正确的顺序执行,它只确保所有任务在完成前都已开始。任务的顺序由应用程序的逻辑决定。
-
WaitGroup 和 channel 有什么区别?
channel 可以用来传递数据和同步任务,而 WaitGroup 只用于同步任务。channel 更灵活,但使用起来也更复杂。
-
WaitGroup 和 mutex 有什么区别?
mutex 可以用来保护共享数据,而 WaitGroup 用于同步任务。mutex 提供更精细的控制,但使用起来也更复杂。
-
WaitGroup 有什么性能影响?
WaitGroup 的性能开销很小,它通常不会对应用程序的性能产生显著影响。
-
何时应该使用 WaitGroup?
当需要同步多个任务时,应该使用 WaitGroup。例如,当需要等待一组协程完成时,或者当需要等待一组文件 I/O 操作完成时。