【Go语言基础【14】】defer与异常处理(panic、recover)

文章目录

  • 零、概述
  • 一、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,恢复程序执行,避免崩溃
执行顺序 deferreturnpanic(若未捕获)
最佳实践 defer用于资源管理,panic/recover用于不可恢复的严重错误处理

注意事项:

  1. defer的性能影响
    • 每个defer会创建一个栈帧,过多使用可能影响性能(尤其在高频调用的函数中)。
    • 建议仅在必要场景(如资源释放)中使用defer,避免滥用。
  2. panic的合理使用
    • 不建议 在业务逻辑中滥用panic,应优先使用多返回值(如(result, error))处理可预期的错误。
    • 推荐场景:处理不可恢复的严重错误(如配置文件缺失、数据库连接失败)。
  3. 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
}
相关推荐
孔令飞6 小时前
Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
ai·云原生·容器·golang·kubernetes
我的golang之路果然有问题12 小时前
云服务器部署Gin+gorm 项目 demo
运维·服务器·后端·学习·golang·gin
孔令飞13 小时前
Go 为何天生适合云原生?
ai·云原生·容器·golang·kubernetes
YGGP16 小时前
吃透 Golang 基础:数据结构之 Map
开发语言·数据结构·golang
march of Time16 小时前
go工具库:hertz api框架 hertz client的使用
开发语言·golang·iphone
余厌厌厌17 小时前
go语言学习 第9章:映射(Map)
服务器·学习·golang
roman_日积跬步-终至千里18 小时前
【Go语言基础【15】】数组:固定长度的连续存储结构
golang
cccc来财19 小时前
Go中的协程并发和并发panic处理
开发语言·后端·golang
roman_日积跬步-终至千里1 天前
【Go语言基础【9】】字符串格式化与输入处理
golang