文章目录
- 文件操作:确保文件句柄关闭。 func readFile(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() // 确保函数返回前关闭文件 // 处理文件内容… return nil } 锁释放:防止死锁。 var mu sync.Mutex func updateData() { mu.Lock() defer mu.Unlock() // 函数退出时自动释放锁 // 修改共享数据… }
- 数据库或业务逻辑中,确保操作失败时回滚。 func transferMoney() { tx := db.Begin() defer func() { if r := recover(); r != nil { // 结合recover处理panic tx.Rollback() } }() // 执行转账操作,可能触发panic tx.Commit() }
- 防止因未处理的panic导致程序崩溃。 func safeCall() { defer func() { if r := recover(); r != nil { fmt.Println(“Recovered from panic:”, r) } }() // 可能触发panic的代码 panic(“unexpected error”) }
- 避免某个Goroutine的panic影响整个程序。 func startWorker() { go func() { defer func() { if r := recover(); r != nil { log.Println(“Worker panic:”, r) } }() // Goroutine的业务逻辑… }() }
- 将panic转换为错误,保持服务可用性。 func safeHandler() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf(“internal error: %v”, r) } }() // 可能panic的代码 return nil }
- func safeCall() { // 直接调用recover(不在defer中) if r := recover(); r != nil { fmt.Println(“Recovered from panic:”, r) } // 触发新的panic panic(“unexpected error”) } // 将safeCall包裹在defer中 defer func() { safeCall() }()
- recover未在defer中调用: safeCall中的recover直接调用,而非通过defer注册的函数。此时recover会在safeCall正常执行时立即触发,而非在panic发生后被动调用。 若此时未发生panic,recover返回nil,无法捕获后续触发的panic。 panic与defer执行顺序: 当外层函数触发panic时,会先执行已注册的defer函数。 在defer中调用safeCall,此时safeCall内的recover尝试捕获当前panic,但随后safeCall自身又触发了一个新的panic("unexpected error"),而新的panic未被任何recover处理,导致程序崩溃。
- 将recover放在defer函数中,并直接与可能触发panic的代码关联: func safeCall() { // 可能触发panic的代码 defer func() { // 在defer中调用recover if r := recover(); r != nil { fmt.Println(“Recovered from panic:”, r) } }() panic(“unexpected error”) } // 注册defer defer safeCall()
- 调用defer safeCall(),注册safeCall到外层函数的defer栈。 当外层函数触发panic时,执行safeCall。 safeCall内部的defer函数中的recover会捕获当前panic,阻止其继续传播。 若safeCall自身触发panic,该panic会被其自身的defer recover捕获。
目录
- 1. defer 的应用场景
- 资源释放
- 事务回滚
- 2. recover 的应用场景
- 全局异常恢复
- 保护Goroutine
- 优雅降级
- 3. 结合使用示例
- 4. 注意事项
- 5. 问题分析
- 代码示例
- 关键问题
- 6. 正确写法
- 修复方案
- 执行逻辑
- 7. 错误示例的详细解释
- 原代码执行流程
- 关键结论
- 总结
recover运行的条件:
- 该协程必须出现了panic
- recover函数必须在和panic同级的defer中被调用
在Go语言中,defer和recover是两个关键特性,通常结合使用以处理资源管理和异常恢复。以下是它们的核心应用场景及使用示例:
defer用于延迟执行函数调用,确保在函数退出前执行特定操作。主要用途包括:
-
文件操作:确保文件句柄关闭。
func readFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // 确保函数返回前关闭文件
// 处理文件内容...
return nil
}
-
锁释放:防止死锁。
var mu sync.Mutex
func updateData() {
mu.Lock()
defer mu.Unlock() // 函数退出时自动释放锁
// 修改共享数据...
}
文件操作:确保文件句柄关闭。
func readFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close() // 确保函数返回前关闭文件
// 处理文件内容...
return nil
}
锁释放:防止死锁。
var mu sync.Mutex
func updateData() {
mu.Lock()
defer mu.Unlock() // 函数退出时自动释放锁
// 修改共享数据...
}
- 数据库或业务逻辑中,确保操作失败时回滚。
func transferMoney() {
tx := db.Begin()
defer func() {
if r := recover(); r != nil { // 结合recover处理panic
tx.Rollback()
}
}()
// 执行转账操作,可能触发panic
tx.Commit()
}
func transferMoney() {
tx := db.Begin()
defer func() {
if r := recover(); r != nil { // 结合recover处理panic
tx.Rollback()
}
}()
// 执行转账操作,可能触发panic
tx.Commit()
}
recover用于捕获panic,防止程序非正常终止。必须在defer函数中调用。
- 防止因未处理的
panic导致程序崩溃。
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
// 可能触发panic的代码
panic("unexpected error")
}
panic导致程序崩溃。
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
// 可能触发panic的代码
panic("unexpected error")
}
- 避免某个Goroutine的
panic影响整个程序。
func startWorker() {
go func() {
defer func() {
if r := recover(); r != nil {
log.Println("Worker panic:", r)
}
}()
// Goroutine的业务逻辑...
}()
}
panic影响整个程序。
func startWorker() {
go func() {
defer func() {
if r := recover(); r != nil {
log.Println("Worker panic:", r)
}
}()
// Goroutine的业务逻辑...
}()
}
- 将
panic转换为错误,保持服务可用性。
func safeHandler() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v", r)
}
}()
// 可能panic的代码
return nil
}
panic转换为错误,保持服务可用性。
func safeHandler() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("internal error: %v", r)
}
}()
// 可能panic的代码
return nil
}
func processRequest() (err error) {
// 恢复panic并转为错误
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic occurred: %v", r)
}
}()
// 资源管理示例
file, err := os.Open("data.txt")
if err != nil {
return err
}
defer file.Close() // 确保文件关闭
// 业务逻辑(可能触发panic)
if someCondition {
panic("data corruption")
}
return nil
}
recover仅在defer中有效:非defer上下文中调用会返回nil。
- 避免滥用
recover:隐藏panic可能导致未知状态,应仅在必要时使用。
- 明确错误处理:优先返回错误而非依赖
panic/recover,后者适用于不可恢复的异常(如程序逻辑错误)。
recover仅在defer中有效:非defer上下文中调用会返回nil。recover:隐藏panic可能导致未知状态,应仅在必要时使用。panic/recover,后者适用于不可恢复的异常(如程序逻辑错误)。通过合理使用defer和recover,可以显著提升Go程序的健壮性和可维护性,尤其在资源管理和异常恢复场景中。
在Go语言中,上述代码无法正确捕获panic,原因如下:
func safeCall() {
// 直接调用recover(不在defer中)
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
// 触发新的panic
panic("unexpected error")
}
// 将safeCall包裹在defer中
defer func() { safeCall() }()
-
recover未在defer中调用:
safeCall中的recover直接调用,而非通过defer注册的函数。此时recover会在safeCall正常执行时立即触发,而非在panic发生后被动调用。
- 若此时未发生
panic,recover返回nil,无法捕获后续触发的panic。
-
panic与defer执行顺序:
- 当外层函数触发
panic时,会先执行已注册的defer函数。
- 在
defer中调用safeCall,此时safeCall内的recover尝试捕获当前panic,但随后safeCall自身又触发了一个新的panic("unexpected error"),而新的panic未被任何recover处理,导致程序崩溃。
recover未在defer中调用:
safeCall中的recover直接调用,而非通过defer注册的函数。此时recover会在safeCall正常执行时立即触发,而非在panic发生后被动调用。- 若此时未发生
panic,recover返回nil,无法捕获后续触发的panic。
panic与defer执行顺序:
- 当外层函数触发
panic时,会先执行已注册的defer函数。 - 在
defer中调用safeCall,此时safeCall内的recover尝试捕获当前panic,但随后safeCall自身又触发了一个新的panic("unexpected error"),而新的panic未被任何recover处理,导致程序崩溃。
将recover放在defer函数中,并直接与可能触发panic的代码关联:
func safeCall() {
// 可能触发panic的代码
defer func() {
// 在defer中调用recover
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("unexpected error")
}
// 注册defer
defer safeCall()
- 调用
defer safeCall(),注册safeCall到外层函数的defer栈。
- 当外层函数触发
panic时,执行safeCall。
safeCall内部的defer函数中的recover会捕获当前panic,阻止其继续传播。
- 若
safeCall自身触发panic,该panic会被其自身的defer recover捕获。
defer safeCall(),注册safeCall到外层函数的defer栈。panic时,执行safeCall。safeCall内部的defer函数中的recover会捕获当前panic,阻止其继续传播。safeCall自身触发panic,该panic会被其自身的defer recover捕获。
假设外层函数触发panic:
- 外层函数执行
panic("outer panic")。 - 程序开始处理
defer,调用defer func() { safeCall() }()。 safeCall执行:recover()尝试捕获外层panic("outer panic"),打印恢复信息。- 随后触发新的
panic("unexpected error")。
- 新的panic未被任何recover处理,导致程序崩溃。
recover必须通过defer注册的函数被动调用,才能捕获到panic。
- 若在普通代码中直接调用
recover,只有在已发生panic且未被处理时才会生效。
recover必须通过defer注册的函数被动调用,才能捕获到panic。recover,只有在已发生panic且未被处理时才会生效。
- 必须将recover放在defer函数中,才能确保在
panic发生后被动调用。
- 避免在恢复逻辑中触发新的panic,否则需要额外的
recover处理。
- 正确的
defer和recover组合是资源管理和异常恢复的核心模式。
panic发生后被动调用。recover处理。defer和recover组合是资源管理和异常恢复的核心模式。通过调整代码结构,确保recover在defer中调用,即可正确捕获并处理panic。
到此这篇关于Golang中defer与recover组合使用的文章就介绍到这了,更多相关Go defer与recover组合使用内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!
您可能感兴趣的文章:
- GoLang中panic与recover函数以及defer语句超详细讲解
- Go基础教程系列之defer、panic和recover详解
- Go语言使用defer+recover解决panic导致程序崩溃的问题
- Golang异常处理之defer,panic,recover的使用详解