优化WaitGroup使用提升性能
- 减少不必要等待:在并发任务执行过程中,确保每个等待的任务确实有必要完成整个计算过程。例如,如果某些任务只是做一些简单的检查或者数据预处理,且这些任务结果并非后续任务必须依赖的,可考虑在主线程中异步执行这些任务,而不是让主线程等待它们通过WaitGroup完成。
- 合理分配任务数量:避免创建过多过于细粒度的任务导致WaitGroup管理开销过大。如果将任务划分得过细,会导致每个任务的执行时间极短,而创建和管理这些任务以及WaitGroup的同步操作所带来的开销相对较大。可适当合并一些关联性强且执行时间较短的任务,提高整体执行效率。
- 复用WaitGroup:在多次需要等待一组并发任务完成的场景下,尽量复用同一个WaitGroup实例,而不是每次都创建新的实例。创建新的WaitGroup实例会带来额外的内存分配和初始化开销,复用可以减少这种开销。
可能导致死锁的场景及避免方法
- 场景一:未调用Done()
- 描述:在并发任务中忘记调用WaitGroup的Done()方法,导致主线程在调用Wait()时一直等待,无法结束,从而产生死锁。
- 避免方法:在每个并发任务的结尾处,确保正确调用WaitGroup的Done()方法。可以使用defer语句,这样即使任务执行过程中发生错误,也能保证Done()方法被调用。例如:
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// 具体任务逻辑
}()
wg.Wait()
- 场景二:Add()在Go 协程中调用
- 描述:在Go协程内部调用WaitGroup的Add()方法,且协程启动后立即开始等待,可能会出现主协程等待子协程执行Add(),而子协程又在等待主协程执行后续逻辑从而导致死锁。
- 避免方法:确保在启动Go协程之前调用Add()方法,明确设置需要等待的任务数量。例如:
var wg sync.WaitGroup
wg.Add(1)
go func() {
// 具体任务逻辑
wg.Done()
}()
wg.Wait()
- 场景三:多次调用Wait()
- 描述:如果在不同的地方多次对同一个WaitGroup调用Wait()方法,而第一次调用Wait()时所有任务已经完成,第二次调用就会永远阻塞,导致死锁。
- 避免方法:明确WaitGroup的使用逻辑,确保只在合适的位置且只调用一次Wait()方法等待所有任务完成。如果确实需要在不同阶段等待不同的任务组,可以考虑使用多个WaitGroup实例。