面试题答案
一键面试性能问题和陷阱分析
- 阻塞性能问题:
- 问题:如果
WaitGroup
等待的协程数量过多,主线程在调用wg.Wait()
时会长时间阻塞,影响整体系统的响应性。例如,在一个电商秒杀系统中,大量商品的库存校验协程都通过WaitGroup
等待,如果库存校验协程过多,主线程等待这些协程完成的时间过长,可能导致用户长时间等待响应。 - 原因:
WaitGroup
底层是通过信号量实现等待机制,大量协程等待时会频繁进行信号量操作,增加内核态与用户态的切换开销。
- 问题:如果
- 死锁问题:
- 问题:如果在协程内部忘记调用
wg.Done()
,或者wg.Add()
的数量与wg.Done()
的数量不匹配,会导致主线程在wg.Wait()
处永远阻塞,形成死锁。比如在一个分布式任务调度系统中,某个任务执行协程由于异常未调用wg.Done()
,调度主线程就会一直等待该任务完成,造成死锁。 - 原因:编程逻辑错误,对
WaitGroup
的使用规则不熟悉。
- 问题:如果在协程内部忘记调用
- 过早释放资源问题:
- 问题:如果在协程还未开始执行任务就调用
wg.Done()
,可能导致资源在任务未完成时就被释放。例如在一个文件读取任务中,协程还未开始读取文件就调用了wg.Done()
,主线程认为任务完成开始后续操作,可能导致文件读取不完整。 - 原因:代码逻辑混乱,协程启动与
wg.Done()
调用顺序错误。
- 问题:如果在协程还未开始执行任务就调用
优化方案
- 减少阻塞影响:
- 方案:可以采用分阶段等待的方式,将大量协程分成多个组,每组使用一个
WaitGroup
,主线程分阶段等待,减少单次等待协程的数量。例如在上述电商秒杀系统中,将商品按类别分组,每个类别商品的库存校验协程使用一个WaitGroup
,主线程依次等待各个组的库存校验完成。
- 方案:可以采用分阶段等待的方式,将大量协程分成多个组,每组使用一个
- 避免死锁:
- 方案:
- 仔细检查代码逻辑,确保
wg.Add()
和wg.Done()
的调用配对。可以在代码关键位置添加日志记录wg.Add()
和wg.Done()
的调用情况,方便排查问题。 - 使用
defer wg.Done()
在协程函数结束时自动调用wg.Done()
,确保即使协程出现异常也能正确通知WaitGroup
任务完成。例如在分布式任务调度系统的任务执行协程中,使用defer wg.Done()
来避免因异常导致未调用wg.Done()
的情况。
- 仔细检查代码逻辑,确保
- 方案:
- 防止过早释放资源:
- 方案:确保
wg.Done()
在协程任务完成时调用,合理安排代码顺序。例如在文件读取任务中,在文件读取操作完成并检查无误后再调用wg.Done()
。
- 方案:确保
实际案例及避免方法
案例:一个在线教育平台的课程资源预加载功能,需要从多个存储节点预加载课程视频、文档等资源。每个资源预加载启动一个协程,使用WaitGroup
等待所有预加载任务完成。
避免问题方法:
- 减少阻塞影响:将课程资源按类型(视频、文档等)分组,每个类型的资源预加载协程使用一个
WaitGroup
。主线程先等待视频资源预加载完成,再等待文档资源预加载完成等,这样减少了单次等待的协程数量,提高了系统响应性。 - 避免死锁:在每个预加载协程函数开头使用
defer wg.Done()
,确保无论协程执行过程中是否出现异常,都能正确通知WaitGroup
任务完成。同时,在代码中添加日志记录每个协程的wg.Add()
和wg.Done()
调用,方便排查问题。 - 防止过早释放资源:在每个预加载协程中,只有在资源成功下载并校验无误后才调用
wg.Done()
,保证资源预加载任务真正完成。