文章目录
- 零、概述
- 一、defer关键字:延迟执行机制
-
- [1. 基本原理](#1. 基本原理)
- [2. defer下变量的作用域](#2. defer下变量的作用域)
- [3. 典型应用场景](#3. 典型应用场景)
- [二、异常处理机制:panic(类java throw)与recover(捕获)](#二、异常处理机制:panic(类java throw)与recover(捕获))
-
- [1. panic:触发异常](#1. panic:触发异常)
- [2. recover:捕获异常](#2. recover:捕获异常)
- 三、defer与recover结合使用
-
- [1. 通用异常处理模板](#1. 通用异常处理模板)
- [2. 多层调用中的异常传递](#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语句,永远也不会忘记关闭、善后等操作
执行顺序示例
go
func deferOrder() {
fmt.Println("开始执行")
defer fmt.Println(" defer 1") // 压栈顺序:1 → 2 → 3
defer fmt.Println(" defer 2")
defer fmt.Println(" defer 3")
fmt.Println("执行结束") // 先于 defer 执行
}
// 输出:
// 开始执行
// 执行结束
// defer 3 (最后压栈,最先执行)
// defer 2
// defer 1
2. defer下变量的作用域
(1)值拷贝机制
go
func deferValueCopy() {
x := 10
defer fmt.Println("defer值拷贝:", x) // 声明时拷贝x的值(10)
x = 20 // 修改不影响 defer 中的值
fmt.Println("修改后x:", x) // 输出:20
}
// defer输出:defer值拷贝:10
(2)闭包引用
go
func deferClosure() {
x := 10
defer func() { // 闭包引用外部变量x(非值拷贝)
fmt.Println("闭包引用:", x) // 取函数退出时的x值
}()
x = 20
fmt.Println("修改后x:", x) // 输出:20
}
// defer输出:闭包引用:20
defer 延迟执行的是一个匿名函数(闭包),闭包不直接捕获值,而是引用外部变量 x。
3. 典型应用场景
(1)资源释放(文件、网络连接等)
go
func readFile(path string) {
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer file.Close() // 确保文件最终关闭
// 处理文件逻辑
}
(2)记录日志或统计耗时
go
func process() {
start := time.Now()
defer func() { // 函数结束时打印耗时
fmt.Printf("处理耗时:%v\n", time.Since(start))
}()
// 模拟业务逻辑
time.Sleep(1 * time.Second)
}
(3)错误处理中的资源回滚
go
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 运行时自动抛出(如内存越界、类型断言失败)。
示例:
go
显式panic
func 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
函数,但当前函数会立即退出,后续代码不执行。
go
正确用法:
func safeDivide(a, b int) {
defer func() {
if err := recover(); err != nil { // 捕获panic
fmt.Println("捕获异常:", err) // 输出:捕获异常:除数不能为零
}
}()
if b == 0 {
panic("除数不能为零") // 触发异常,被defer捕获
}
fmt.Println(a / b)
}
func main() {
safeDivide(10, 0) // 正常执行,不崩溃
}
错误用法:
func wrongRecover() {
recover() // 非defer中调用,无效
panic("测试") // 未捕获,程序崩溃
}
三、defer与recover结合使用
1. 通用异常处理模板
go
func protect(fn func()) { // 保护任意函数防止panic
defer func() {
if err := recover(); err != nil {
fmt.Println("全局异常处理:", err)
}
}()
fn() // 执行可能panic的函数
}
func main() {
protect(func() {
panic("业务逻辑异常") // 被保护函数捕获
})
}
2. 多层调用中的异常传递
go
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
}