面试题答案
一键面试可能存在的问题
- 不必要的等待:如果在某些goroutine完成工作后,其他goroutine还在等待不必要的操作,会导致整体性能下降。例如,有些goroutine已经完成了核心任务,但因为WaitGroup还未释放,仍然处于等待状态。
- 过大的粒度:如果将大量不同功能、不同重要性的goroutine都放在同一个WaitGroup下,可能导致整体同步过于粗粒度,部分goroutine的阻塞会影响到其他已经完成的goroutine。
- 频繁的创建和销毁:如果在高并发场景下频繁地创建和销毁WaitGroup实例,会增加系统开销。
优化方案
- 分拆WaitGroup:根据功能或者执行时间将goroutine分组,使用多个WaitGroup进行同步,这样不同组的goroutine可以独立完成和等待,减少不必要的等待时间。
- 优化等待逻辑:在确保业务逻辑正确的前提下,尽可能让goroutine完成任务后尽早释放WaitGroup,避免不必要的等待。
- 复用WaitGroup:避免频繁创建和销毁WaitGroup实例,通过复用减少系统开销。
代码示例
package main
import (
"fmt"
"sync"
"time"
)
// 优化前
func unoptimized() {
var wg sync.WaitGroup
numGoroutines := 1000
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 模拟一些工作
time.Sleep(10 * time.Millisecond)
}()
}
start := time.Now()
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("Unoptimized took %s\n", elapsed)
}
// 优化后
func optimized() {
numGroups := 10
var wgs [10]sync.WaitGroup
numGoroutines := 1000
goroutinesPerGroup := numGoroutines / numGroups
for group := 0; group < numGroups; group++ {
for i := 0; i < goroutinesPerGroup; i++ {
wgs[group].Add(1)
go func(g int) {
defer wgs[g].Done()
// 模拟一些工作
time.Sleep(10 * time.Millisecond)
}(group)
}
}
start := time.Now()
for group := 0; group < numGroups; group++ {
wgs[group].Wait()
}
elapsed := time.Since(start)
fmt.Printf("Optimized took %s\n", elapsed)
}
func main() {
unoptimized()
optimized()
}
在上述代码中,unoptimized
函数使用一个WaitGroup
来同步所有的goroutine
。而optimized
函数将goroutine
分成10组,每组使用一个WaitGroup
,这样不同组的goroutine
可以独立完成任务并等待,减少了整体的等待时间。运行结果可以看到优化后的版本执行时间更短,性能得到提升。