面试题答案
一键面试设计思路
- 在每个子
goroutine
中使用defer
语句来捕获panic
。 - 当
panic
发生时,通过recover
函数捕获panic
信息。 - 将捕获到的
panic
信息通过通道(channel
)传递给主goroutine
进行处理。 - 主
goroutine
从通道接收panic
信息并进行相应处理,同时不影响其他正常运行的goroutine
。
关键代码实现
package main
import (
"fmt"
)
func worker(id int, resultChan chan int, panicChan chan interface{}) {
defer func() {
if r := recover(); r != nil {
panicChan <- fmt.Sprintf("goroutine %d panicked: %v", id, r)
}
}()
// 模拟可能发生panic的任务
if id == 3 {
panic("模拟数据处理错误")
}
result := id * 2
resultChan <- result
}
func main() {
numGoroutines := 5
resultChan := make(chan int, numGoroutines)
panicChan := make(chan interface{}, numGoroutines)
for i := 1; i <= numGoroutines; i++ {
go worker(i, resultChan, panicChan)
}
go func() {
for p := range panicChan {
fmt.Println(p)
}
}()
for i := 1; i <= numGoroutines; i++ {
select {
case result := <-resultChan:
fmt.Printf("goroutine %d 计算结果: %d\n", i, result)
}
}
close(resultChan)
close(panicChan)
}
可能面临的问题及解决方案
问题
- 通道阻塞:如果主
goroutine
没有及时从panicChan
中读取数据,可能导致panicChan
阻塞,进而影响子goroutine
的recover
操作,使得子goroutine
无法正常结束。 - 资源泄漏:如果
goroutine
在panic
后没有正确清理资源,可能导致资源泄漏。
解决方案
- 针对通道阻塞:可以在主
goroutine
中启动一个单独的goroutine
来处理panicChan
中的数据,如代码中的go func() { for p := range panicChan { fmt.Println(p) } }()
。这样可以确保panicChan
不会阻塞。 - 针对资源泄漏:在
defer
函数中除了使用recover
捕获panic
,还应进行必要的资源清理操作,例如关闭文件、数据库连接等。例如:
func worker(id int, resultChan chan int, panicChan chan interface{}) {
file, err := os.Open("test.txt")
if err != nil {
panic(fmt.Sprintf("打开文件失败: %v", err))
}
defer func() {
file.Close()
if r := recover(); r != nil {
panicChan <- fmt.Sprintf("goroutine %d panicked: %v", id, r)
}
}()
// 模拟可能发生panic的任务
if id == 3 {
panic("模拟数据处理错误")
}
result := id * 2
resultChan <- result
}
这样可以确保在goroutine
发生panic
时,文件资源能被正确关闭,避免资源泄漏。