panic仅终止当前goroutine,程序是否退出取决于是否发生在main或所有非daemon goroutine;必须用defer+recover捕获同goroutine的panic,运行时越界、nil解引用、类型断言失败、除零、向已关闭channel发送数据会触发不可恢复panic。

panic 不会直接让整个程序退出,只杀当前 goroutine;但若发生在 main 或最后一个活跃 goroutine,进程就真挂了。必须用 defer + recover 拦,且只能拦同 goroutine 的 panic。
哪些操作会触发 panic
Go 运行时对几类明显非法行为做即时拦截,一碰就崩:
-
数组/切片索引越界:比如arr[5]但len(arr) == 3 -
nil 指针解引用:如var p *int; fmt.Println(*p) -
类型断言失败:接口值不是目标类型,i.(string)却存了int -
除零:10 / 0直接触发runtime error: integer divide by zero -
向已关闭的 channel 发送数据:ch 但 <code>close(ch)已执行 -
关闭 nil channel或关闭已关闭的 channel -
字符串索引越界:s[10]但len(s) == 5
这些都不是“可能 panic”,而是**确定 panic**——编译器不报错,运行到那一步必炸。
recover 必须写在 defer 函数里,且时机不能错
recover() 离开 defer 就是废函数,返回永远是 nil。常见失效写法:
立即学习“go语言免费学习笔记(深入)”;
- 把
recover()放在普通函数里,没包在defer func() { ... }()中 -
defer语句写在panic()后面(比如条件分支里),根本没注册上 - 在 goroutine A 里启动 goroutine B,想在 A 里
recover()B 的 panic → 跨 goroutine 无效
正确姿势只有一种:defer func() { if r := recover(); r != nil { /* 处理 */ } }(),且该 defer 必须在 panic 触发前已执行(通常放在函数开头)。
HTTP handler 和长期 goroutine 必须加 recover
这两类场景不加 recover,等于主动放弃服务稳定性:
-
http.HandleFunc注册的 handler:一次 panic 会让当前请求连接中断,客户端收不到任何响应,日志里只有堆栈,无 HTTP 状态码 - 用
go func() { ... }()启的长期任务(如定时拉取、消息消费):panic 后 goroutine 静默退出,任务永久丢失,资源(如文件句柄、DB 连接)可能泄漏
示例中 safeHandler 的 defer 块必须存在,且 recover() 调用后要记录日志、返回错误响应,否则等于白加。
recover 后程序继续执行,但状态未必可靠
recover() 成功捕获后,函数会从 defer 返回点继续往下走,但要注意:
- panic 发生点之后的代码全跳过了,局部变量、中间状态可能不一致
- 如果 panic 来自资源释放失败(如
close(file)崩了),recover后别再假设文件还开着 - 不要在
recover后继续用刚出问题的对象(比如越界访问后的切片、断言失败的接口值)
真正安全的做法是:recover 后做清理、记录、返回错误,**不试图“续上”原逻辑**。复杂状态恢复得靠设计,不是靠 recover 补救。


















