面试题答案
一键面试Panic面临的挑战
- 并发程序崩溃:在并发场景下,一个goroutine中发生panic,如果没有恰当的recover,会导致整个程序崩溃。例如:
package main
import (
"fmt"
)
func main() {
go func() {
panic("Some error")
}()
select {}
}
上述代码中,匿名goroutine发生panic,由于没有recover,主程序也会崩溃。 2. 资源泄漏:如果在panic发生时没有正确清理资源(如文件句柄、数据库连接等),会导致资源泄漏。比如:
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
panic(err)
}
defer file.Close()
// 假设这里发生panic,文件会在defer时关闭,但如果没有defer,文件资源将泄漏
panic("Another error")
}
普通错误处理面临的挑战
- 错误传播繁琐:在多层函数调用中,需要层层传递错误,代码变得冗长且易出错。例如:
package main
import (
"fmt"
)
func readFile() (string, error) {
// 模拟读取文件错误
return "", fmt.Errorf("file read error")
}
func processFile() (string, error) {
content, err := readFile()
if err != nil {
return "", err
}
// 对content进行处理,这里省略
return content, nil
}
func main() {
result, err := processFile()
if err != nil {
fmt.Println("Error:", err)
}
}
在复杂的调用链中,每层都要处理错误返回,代码可读性和维护性变差。 2. 难以处理异步错误:在并发场景下,多个goroutine同时运行,获取异步操作的错误变得困难。例如:
package main
import (
"fmt"
"time"
)
func asyncTask() {
// 模拟异步任务执行一段时间后返回错误
time.Sleep(2 * time.Second)
fmt.Println("Task completed with error")
}
func main() {
go asyncTask()
// 这里难以直接获取asyncTask中的错误
time.Sleep(3 * time.Second)
}
合理的错误处理策略
- 区分可恢复和不可恢复错误:对于可恢复错误,使用普通错误处理方式;对于不可恢复错误,使用panic并在合适的地方recover。例如:
package main
import (
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
这里除法中分母为0是可恢复错误,使用普通错误处理。
- 使用recover处理panic:在主goroutine或负责管理的goroutine中使用recover捕获panic,避免程序崩溃。例如:
package main
import (
"fmt"
)
func worker() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("Worker panicked")
}
func main() {
go worker()
select {}
}
这样在worker goroutine发生panic时,通过defer和recover捕获,程序不会崩溃。
- 使用context处理并发错误:在并发操作中,使用context来传递取消信号和处理错误。例如:
package main
import (
"context"
"fmt"
"time"
)
func task(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(2 * time.Second):
return fmt.Errorf("task failed")
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := task(ctx)
if err != nil {
fmt.Println("Error:", err)
}
}
通过context可以有效管理并发任务的生命周期和错误处理。