panic仅终止当前goroutine,程序是否退出取决于是否发生在main或所有非daemon goroutine;必须用defer+recover捕获的三类场景是HTTP handler、长期运行的goroutine、插件/反射调用。

panic 会直接终止当前 goroutine,但主程序不一定崩溃
Go 的 panic 不等同于其他语言的“未捕获异常导致进程退出”。它只终止**当前 goroutine**,若发生在 main goroutine 或所有非 daemon goroutine 都 panic,程序才真正退出。这意味着:你得清楚 panic 发生在哪——是 HTTP handler?定时任务?还是初始化逻辑?不同位置影响完全不同。
常见误判是看到日志里有 panic: runtime error: index out of range 就以为服务挂了,其实可能只是某个请求失败,其他请求照常处理。可通过 recover 捕获并记录,避免级联影响。
必须用 defer + recover 捕获 panic 的三个典型场景
不是所有 panic 都该 recover,但以下三类必须加:
- HTTP handler 函数(如
http.HandleFunc注册的函数)——否则一次 panic 会让整个连接中断,且无错误响应给客户端 - goroutine 启动的长期运行逻辑(如
go func() { ... }())——不 recover 会导致 goroutine 静默死亡,泄漏资源或丢任务 - 插件式调用或反射执行的代码(如通过
plugin.Open或reflect.Value.Call)——调用方无法预知被调用代码是否健壮
示例:在 handler 中安全 recover
立即学习“go语言免费学习笔记(深入)”;
func safeHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("panic in handler: %v", err)
}
}()
// 可能 panic 的业务逻辑
doSomething(r)
}
recover 只在 defer 函数中有效,且仅对同 goroutine 的 panic 生效
这是最容易踩的坑:recover() 必须写在 defer 函数里,且该 defer 必须在 panic 发生前已注册;跨 goroutine 调用 recover() 总是返回 nil。
常见错误写法:
- 把
recover()放在普通函数里,没包在defer中 → 永远不生效 - 在 goroutine 内部启动另一个 goroutine,并试图在外部 recover → 作用域错配
- defer 语句写在 panic 之后(比如条件分支里)→ 注册时机太晚,来不及捕获
正确姿势:每个可能 panic 的 goroutine 都要有自己的 defer func() { recover() }()。
不要用 panic 替代错误返回,尤其在导出函数和公共接口中
Go 的哲学是“error 是值”,panic 应只用于**真正不可恢复的编程错误**,比如 nil 指针解引用、断言失败、channel 关闭后写入。业务错误(如参数校验失败、数据库查不到记录)必须用 error 返回。
导出函数(首字母大写)若随意 panic,调用方无法预期,也无法用 recover 安全包裹——因为不知道该不该 recover,也不知道 panic 的类型和含义。这破坏了 Go 的显式错误控制流。
一个简单判断标准:如果这个错误能在测试中稳定复现,且修复后就不该再发生,那才是 panic 的合理位置;否则,它大概率该是一个 error。
真正难处理的是 panic 的传播边界——比如中间件链中某一层 panic,上层 middleware 是否该 recover?recover 后要不要继续执行后续中间件?这些细节没有银弹,得按调用链的信任关系来设计,而不是靠堆 recover 解决。










