【Go语言基础【14】】defer与异常处理(panic、recover)
文章目录
- 零、概述
- 一、defer关键字:延迟执行机制
- 1. 基本原理
- 2. defer下变量的作用域
- 3. 典型应用场景
- 二、异常处理机制:panic(类java throw)与recover(捕获)
- 1. panic:触发异常
- 2. recover:捕获异常
- 三、defer与recover结合使用
- 1. 通用异常处理模板
- 2. 多层调用中的异常传递
零、概述
关键字/函数 | 作用描述 |
---|---|
defer | 延迟执行函数,用于资源释放、日志记录等,按LIFO顺序执行 |
panic | 触发异常,终止当前协程正常执行,可显式调用或由运行时错误触发 |
recover | 在defer 中捕获panic ,恢复程序执行,避免崩溃 |
执行顺序 | defer → return → panic (若未捕获) |
最佳实践 | defer 用于资源管理,panic /recover 用于不可恢复的严重错误处理 |
注意事项:
- defer的性能影响
- 每个
defer
会创建一个栈帧,过多使用可能影响性能(尤其在高频调用的函数中)。- 建议仅在必要场景(如资源释放)中使用
defer
,避免滥用。- panic的合理使用
- 不建议在业务逻辑中滥用
panic
,应优先使用多返回值(如(result, error)
)处理可预期的错误。- 推荐场景:处理不可恢复的严重错误(如配置文件缺失、数据库连接失败)。
- recover的作用范围
recover
仅能捕获当前协程的panic
,无法跨协程捕获(如其他goroutine
中的panic
)。- 跨协程异常处理需通过通道(
channel
)传递错误。
一、defer关键字:延迟执行机制
1. 基本原理
核心特性
- 独立的defer栈,延迟执行:
defer
修饰的函数或语句会在当前函数即将退出时执行(包括正常返回、panic
异常或提前return
)。 - 栈存储:多个
defer
按**后进先出(LIFO)**顺序执行,先声明的后执行。 - 值拷贝:
defer
语句中的参数在声明时立即求值并拷贝,后续修改不影响其值。
defer的作用
一般用来做善后操作,例如清理垃圾、释放资源,无论是否报错都执行defer对象。另一方面,defer可以让这些善后操作的语句和开始语句放在一起,无论在可读性上还是安全性上都很有改善,毕竟写完开始语句就可以直接写defer语句,永远也不会忘记关闭、善后等操作
执行顺序示例
func deferOrder() {fmt.Println("开始执行")defer fmt.Println(" defer 1") // 压栈顺序:1 → 2 → 3defer fmt.Println(" defer 2")defer fmt.Println(" defer 3")fmt.Println("执行结束") // 先于 defer 执行
}
// 输出:
// 开始执行
// 执行结束
// defer 3 (最后压栈,最先执行)
// defer 2
// defer 1
2. defer下变量的作用域
(1)值拷贝机制
func deferValueCopy() {x := 10defer fmt.Println("defer值拷贝:", x) // 声明时拷贝x的值(10)x = 20 // 修改不影响 defer 中的值fmt.Println("修改后x:", x) // 输出:20
}
// defer输出:defer值拷贝:10
(2)闭包引用
func deferClosure() {x := 10defer func() { // 闭包引用外部变量x(非值拷贝)fmt.Println("闭包引用:", x) // 取函数退出时的x值}()x = 20fmt.Println("修改后x:", x) // 输出:20
}
// defer输出:闭包引用:20
defer 延迟执行的是一个匿名函数(闭包),闭包不直接捕获值,而是引用外部变量 x。
3. 典型应用场景
(1)资源释放(文件、网络连接等)
func readFile(path string) {file, err := os.Open(path)if err != nil {panic(err)}defer file.Close() // 确保文件最终关闭// 处理文件逻辑
}
(2)记录日志或统计耗时
func process() {start := time.Now()defer func() { // 函数结束时打印耗时fmt.Printf("处理耗时:%v\n", time.Since(start))}()// 模拟业务逻辑time.Sleep(1 * time.Second)
}
(3)错误处理中的资源回滚
func transaction() {db, err := openDB()if err != nil {panic(err)}defer db.Rollback() // 事务失败时回滚(需配合 recover)// 执行数据库操作db.Commit() // 成功则提交
}
二、异常处理机制:panic(类java throw)与recover(捕获)
1. panic:触发异常
核心作用:
当程序遇到致命错误(如空指针解引用、除数为零)时,panic会中断当前协程的正常执行,并逐层向上抛出异常,最终导致程序崩溃(除非被recover捕获)。
触发时机:
显式调用:主动调用panic(“错误信息”)触发异常(如业务逻辑错误)。
隐式触发:Go 运行时自动抛出(如内存越界、类型断言失败)。
示例:
显式panicfunc divide(a, b int) int {if b == 0 {panic("除数不能为零") // 触发异常}return a / b
}
func main() {divide(10, 0) // 程序崩溃,输出panic信息
}**运行时panic示例**func runtimePanic() {var ptr *int // nil指针fmt.Println(*ptr) // 运行时panic: invalid memory address
}
2. recover:捕获异常
作用:在defer
函数中调用recover()
,捕获当前协程的panic
并恢复执行。
注意:
- 仅在
defer
修饰的函数中有效,其他位置调用返回nil
。 - 捕获后可继续执行
defer
函数,但当前函数会立即退出,后续代码不执行。
正确用法:
func safeDivide(a, b int) {defer func() {if err := recover(); err != nil { // 捕获panicfmt.Println("捕获异常:", err) // 输出:捕获异常:除数不能为零}}()if b == 0 {panic("除数不能为零") // 触发异常,被defer捕获}fmt.Println(a / b)
}
func main() {safeDivide(10, 0) // 正常执行,不崩溃
}错误用法:
func wrongRecover() {recover() // 非defer中调用,无效panic("测试") // 未捕获,程序崩溃
}
三、defer与recover结合使用
1. 通用异常处理模板
func protect(fn func()) { // 保护任意函数防止panicdefer func() {if err := recover(); err != nil {fmt.Println("全局异常处理:", err)}}()fn() // 执行可能panic的函数
}
func main() {protect(func() {panic("业务逻辑异常") // 被保护函数捕获})
}
2. 多层调用中的异常传递
func level3() {panic("level3 panic") // 触发panic
}
func level2() {level3() // 传递panic
}
func level1() {defer func() {if err := recover(); err != nil {fmt.Println("在level1捕获:", err) // 捕获level3的panic}}()level2() // 调用下层函数
}
func main() {level1() // 输出:在level1捕获:level3 panic
}