面试题答案
一键面试优化WaitGroup的计数管理以提高性能
- 减少不必要的计数操作:尽量避免在循环中反复调用
wg.Add(1)
和wg.Done()
。可以提前计算好需要创建的 goroutine 数量,一次性调用wg.Add(n)
,这样可以减少系统调用开销。 - 使用 sync.Pool 复用 WaitGroup:由于大量 goroutine 频繁创建和结束,可以使用
sync.Pool
来复用WaitGroup
实例,避免频繁的内存分配和垃圾回收。
异常处理机制设计
- 使用 recover 捕获异常:在每个 goroutine 中使用
recover
来捕获运行时的 panic,避免单个 goroutine 的异常导致整个程序崩溃。 - 集中处理异常:将捕获到的异常通过 channel 传递到主 goroutine 或专门的异常处理 goroutine 进行集中处理。
- 资源清理:在捕获到异常后,要确保相关资源(如文件句柄、数据库连接等)被正确关闭和清理,避免资源泄漏。
关键部分代码示例及解释
package main
import (
"fmt"
"sync"
)
// 创建一个 sync.Pool 用于复用 WaitGroup
var wgPool = sync.Pool{
New: func() interface{} {
return new(sync.WaitGroup)
},
}
func worker(id int, resultChan chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
// 捕获异常
fmt.Printf("Worker %d panicked: %v\n", id, r)
}
}()
// 模拟工作逻辑,这里可能会出现异常
if id == 3 {
panic("模拟逻辑错误")
}
resultChan <- id * 2
}
func main() {
numWorkers := 5
resultChan := make(chan int, numWorkers)
// 从 sync.Pool 中获取 WaitGroup
wg := wgPool.Get().(*sync.WaitGroup)
wg.Add(numWorkers)
for i := 1; i <= numWorkers; i++ {
go worker(i, resultChan, wg)
}
go func() {
wg.Wait()
close(resultChan)
// 将 WaitGroup 放回 sync.Pool
wgPool.Put(wg)
}()
for result := range resultChan {
fmt.Printf("Received result: %d\n", result)
}
}
代码解释:
- sync.Pool 复用 WaitGroup:通过
sync.Pool
的New
函数创建sync.WaitGroup
实例,并在使用完毕后通过wgPool.Put(wg)
放回池中,实现复用。 - 异常捕获:在
worker
函数中使用defer recover
来捕获可能出现的 panic,并打印异常信息。 - WaitGroup 同步:主函数中获取
WaitGroup
并添加计数,启动多个 goroutine 后等待所有 goroutine 完成,然后关闭结果通道。 - 结果处理:通过
for... range
从结果通道中读取数据,确保在所有 goroutine 完成后正确处理结果。