面试题答案
一键面试可能遇到的性能瓶颈
- 阻塞问题:WaitGroup的
Wait
方法会阻塞当前goroutine,直到所有被标记的goroutine都调用了Done
。如果远程调用或本地goroutine执行时间过长,会导致主线程长时间阻塞,影响系统整体响应性。 - 资源竞争:在高并发场景下,多个goroutine同时调用
Add
、Done
方法可能会产生资源竞争,虽然Go语言的标准库对WaitGroup做了同步处理,但频繁的操作仍可能带来性能损耗。 - 内存开销:如果有大量的goroutine使用WaitGroup,会消耗较多的内存来维护其状态。
优化方案
- 减少阻塞时间:可以将远程调用进行适当的分组并发,并设置合理的超时机制。对于本地goroutine,如果其执行的任务可以拆分,可以进一步并行化处理。
- 降低资源竞争:尽量减少在高并发场景下对WaitGroup的操作频率,例如在创建goroutine时一次性设置好
Add
的数量,而不是在运行中动态调整。 - 优化内存使用:对于一些短暂运行且数量巨大的goroutine,可以考虑使用sync.Cond等更轻量级的同步机制替代WaitGroup,减少内存占用。
关键代码片段
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 假设这里是多个远程调用任务
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int, ctx context.Context) {
defer wg.Done()
select {
case <-time.After(2 * time.Second): // 模拟远程调用
fmt.Printf("goroutine %d finished\n", id)
case <-ctx.Done():
fmt.Printf("goroutine %d cancelled\n", id)
return
}
}(i, ctx)
}
// 等待所有任务完成,但不会无限阻塞
go func() {
wg.Wait()
cancel()
}()
<-ctx.Done()
fmt.Println("all goroutines either finished or cancelled")
}
在这段代码中:
- 使用
context.WithTimeout
设置了一个5秒的超时,避免因远程调用或本地goroutine执行过久而导致主线程无限期阻塞。 - 通过
select
语句,goroutine可以在完成任务或超时取消时做出相应处理。 - 主线程通过监听
context.Done
通道来得知所有goroutine是否完成或超时,而不是单纯依赖WaitGroup.Wait
的阻塞。这样在一定程度上优化了性能和系统响应性。