panic、defer、recover

panic、defer、recover

panic定义

panic是Go语言的一个内置函数,用于引发运行时的错误,当程序运行到某个不可恢复的错误状态时,可以调用panic函数,panic会立即停止当前函数的正常执行流程,然后逐级执行函数的defer语句(如果有),最后程序退出并输出错误信息(除非在上层调用recover捕获)。

panic("无法恢复的异常")
  • 相当于其他语言的throw异常(但Go没有try/catch)
  • 一旦触发panic:
    1. 当前函数停止执行;
    2. 执行当前goroutine的所有延迟调用(defer);
    3. 继续向上回溯调用栈,直到:
      1. 找到revocer()
      2. 或者回溯到顶层,程序退出

defer

defer语句用于安排函数调用在函数退出时执行,defer通常用于确保某个操作在函数执行结束后发生,无论函数是否发生panic。defer语句经常被用于处理资源释放(如文件关闭、连接关闭)或者错误处理(通过recover函数)。

func demo() {
    defer fmt.Println("defer 输出")
}

recover

recover是一个内建函数,用于从panic中恢复,当panic函数被调用时,它会停止当前函数的执行,并且执行在该函数使用defer关键字延迟的函数,然后返回一个非nil值,如果在defer延迟的函数中调用了recover函数,程序会从panic的状态中恢复过来,继续正常执行后续代码。

recover 可以中止 panic 造成的程序崩溃。 它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用。

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

panic的使用场景

Go官方推荐:panic只用于无法恢复的错误

常见场景
  • 程序内部逻辑出现严重错误

逻辑不可能发生但发生了,例如数据结构被破坏

if node == nil {
    panic("unexpected nil node: tree structure corrupted")
}
  • 初始化阶段的不可恢复错误

比如配置文件缺失,数据库连接参数错误

func init() {
    if os.Getenv("CONF_PATH") == "" {
        panic("missing CONFIG_PATH environment variable")
    }
}
  • 开发调试阶段

用来快速中断程序,并暴露错误(生产环境替换为错误处理)

使用示范

  • 示范1
package main

import "fmt"

func main() {
    fmt.Println("Start")
    panic("something went wrong")
    fmt.Println("This will never be printed")
}

Start
panic: something went wrong

goroutine 1 [running]:
main.main()
    /path/to/main.go:7 +0x39
exit status 2
  • 示范2: panic +defer
package main

import "fmt"

func main() {
    defer fmt.Println("defer 1: will run before panic exits")
    defer fmt.Println("defer 2: also runs")

    fmt.Println("Start")
    panic("unexpected error")
    fmt.Println("Never reached")
}

Start
defer 2: also runs
defer 1: will run before panic exits
panic: unexpected error
...
  • 示范3:panic+recover

recover()必须在defer中调用才有效,否则捕获不到panic。

package main

import "fmt"

func mayPanic() {
    panic("boom!")
}

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

    mayPanic()
    fmt.Println("Program continues after recover")
}

Recovered from panic: boom!
Program continues after recover

panic使用注意事项

  1. 不要用panic当普通错误处理

Go倡导 error return + if err!=nil 处理可恢复错误

panic仅在"无路可走"的时候使用

  1. panic只会影响当前的goroutine

其他goroutine会继续运行,除非主goroutine崩溃退出整个进程。

  1. recover不能跨goroutine捕获

要在panic发生的同一个goroutine中用recover

  1. panic发生时defer仍会执行

这也是释放资源、关闭文件的最后机会

官方推荐的panic使用模式

func MustSomething(ok bool) {
    if !ok {
        panic("something is wrong")
    }
}

命名规范:带Must前缀的函数通常表示会panic(例如 template.Must())。

这种方式明确告诉调用者:

不用检查返回值,因为失败直接panic。

调用方可以选择用recover捕获。

Go panic运行流程

┌──────────────────────┐
│  正常执行 main()      │
└─────────┬────────────┘
          │
          ▼
   触发 panic("error")
          │
          ▼
┌──────────────────────┐
│ 停止当前函数执行      │
│(后续代码不再运行)   │
└─────────┬────────────┘
          │
          ▼
依次执行已注册的 defer(LIFO 顺序)
          │
          ▼
在 defer 中调用 recover() 吗?
        ┌─┴───┐
        │  是 │──► 捕获 panic 值 → 正常返回
        └──┬──┘
           │否
           ▼
    回溯到上层调用函数
           │
           ▼
上层调用有 defer + recover 吗?
        ┌─┴───┐
        │  是 │──► 捕获 panic 值 → 正常返回
        └──┬──┘
           │否
           ▼
   重复执行:执行 defer → 回溯
           │
           ▼
到达最顶层仍未 recover?
           │
           ▼
    程序崩溃,打印 panic 堆栈
posted @ 2025-08-06 19:30  biby  阅读(17)  评论(0)    收藏  举报