panic、defer、recover
panic定义
panic是Go语言的一个内置函数,用于引发运行时的错误,当程序运行到某个不可恢复的错误状态时,可以调用panic函数,panic会立即停止当前函数的正常执行流程,然后逐级执行函数的defer语句(如果有),最后程序退出并输出错误信息(除非在上层调用recover捕获)。
panic("无法恢复的异常")
- 相当于其他语言的throw异常(但Go没有try/catch)
- 一旦触发panic:
- 当前函数停止执行;
- 执行当前goroutine的所有延迟调用(defer);
- 继续向上回溯调用栈,直到:
- 找到revocer()
- 或者回溯到顶层,程序退出
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 mainimport "fmt"func main() {fmt.Println("Start")panic("something went wrong")fmt.Println("This will never be printed")
}Start
panic: something went wronggoroutine 1 [running]:
main.main()/path/to/main.go:7 +0x39
exit status 2
- 示范2: panic +defer
package mainimport "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 mainimport "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使用注意事项
- 不要用panic当普通错误处理
Go倡导 error return + if err!=nil 处理可恢复错误
panic仅在"无路可走"的时候使用
- panic只会影响当前的goroutine
其他goroutine会继续运行,除非主goroutine崩溃退出整个进程。
- recover不能跨goroutine捕获
要在panic发生的同一个goroutine中用recover
- 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 堆栈