如何从 panic 中恢复 ?

参考回答

在 Go 中,可以使用内置的 recover 函数从 panic 中恢复。recover 必须在 defer 中调用,它捕获到的返回值是触发 panic 时传递的值。如果没有发生 panicrecover 返回 nil

以下是一个简单示例:

package main

import "fmt"

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    fmt.Println("Before panic")
    panic("something went wrong")
    fmt.Println("After panic") // 不会执行
}
Go

输出:

Before panic
Recovered from panic: something went wrong

详细讲解与拓展

  1. panicrecover 的工作机制
    • panic:用于中断程序正常的执行流。它会引发运行时错误或手动触发的一种 “紧急状态”。
    • recover:用于在程序崩溃时恢复执行,从而防止程序终止。
  2. defer 的作用
    recover 必须配合 defer 使用,因为 panic 触发后会立即退出当前函数,而 defer 中的代码会在退出前执行。

    示例:

    func safeFunction() {
       defer func() {
           if r := recover(); r != nil {
               fmt.Println("Recovered:", r)
           }
       }()
       fmt.Println("Executing risky operation")
       panic("operation failed")
       fmt.Println("This will not be printed")
    }
    
    func main() {
       safeFunction()
       fmt.Println("Program continues after recovery")
    }
    
    Go

    输出:

    Executing risky operation
    Recovered: operation failed
    Program continues after recovery
    
  3. 实际使用场景
    • 防止程序崩溃: 用于服务端代码,防止单个请求的错误导致整个服务崩溃。
    • 清理资源:defer 中执行 recover 时可以进行资源清理,例如关闭文件、释放锁等。
    • 错误转换:panic 转为错误返回值,便于调用者处理。

    示例:捕获 panic 并返回错误

    func safeDivide(a, b int) (result int, err error) {
       defer func() {
           if r := recover(); r != nil {
               err = fmt.Errorf("panic occurred: %v", r)
           }
       }()
       result = a / b // 如果 b == 0,会触发 panic
       return
    }
    
    func main() {
       result, err := safeDivide(10, 0)
       if err != nil {
           fmt.Println("Error:", err)
       } else {
           fmt.Println("Result:", result)
       }
    }
    
    Go

    输出:

    Error: panic occurred: runtime error: integer divide by zero
    
  4. 需要注意的点
    • 不可滥用 panicrecoverpanic 是用于严重错误的场景,不应该作为普通错误处理的工具。
    • recover 只在当前的 goroutine 有效:如果 panic 发生在其他 goroutinerecover 无法捕获。

    示例:

    func main() {
       go func() {
           defer func() {
               if r := recover(); r != nil {
                   fmt.Println("Recovered in goroutine:", r)
               }
           }()
           panic("goroutine panic")
       }()
    
       // 主 goroutine 不受影响
       fmt.Println("Main goroutine continues")
    }
    
    Go
  5. 多层函数调用中的 panic
    如果 panic 在深层函数中触发,但 recover 在高层函数中使用,它仍然能捕获到:

    func deep() {
       panic("deep panic")
    }
    
    func wrapper() {
       defer func() {
           if r := recover(); r != nil {
               fmt.Println("Recovered:", r)
           }
       }()
       deep()
    }
    
    func main() {
       wrapper()
       fmt.Println("Program continues after recovery")
    }
    
    Go

    输出:

    Recovered: deep panic
    Program continues after recovery
    

总结

  • 使用 recoverpanic 中恢复,必须配合 defer 使用。
  • 典型场景包括防止程序崩溃、清理资源和错误转换。
  • recover 应谨慎使用,避免掩盖真实错误,panic 应保留给不可恢复的严重问题。
  • 注意 recover 的作用范围仅限于当前 goroutine

发表评论

后才能评论