recover必须在每个goroutine内单独defer,否则收不到panic;高并发下主goroutine的defer对子goroutine无效;每个worker需内部defer recover;闭包中defer须写在闭包内;panic应通过带缓冲channel统一收集,避免并发写日志。

recover 必须在每个 goroutine 内单独 defer,否则收不到 panic
高并发下大量 go func() { ... }() 启动子协程,主 goroutine 的 defer recover() 对它们完全无效。这是最常被忽略的致命错误——panic 发生在哪个 goroutine,就必须在那个 goroutine 里用 defer 捕获。
- 错误写法:
go worker()外层统一 defer,worker 内 panic 仍会直接崩溃 - 正确写法:每个 worker 函数开头就写
defer func() { if r := recover(); r != nil { ... } }() - 若 worker 是闭包形式,
defer必须写在闭包内部,不能写在外围函数体里
用带缓冲 channel 统一收集 panic,别直接打日志
成百上千 goroutine 同时 panic 并发写日志,极易触发 I/O 阻塞或 panic 丢失。必须用 channel 做缓冲中转,再由单个 consumer 消费。
- channel 缓冲大小建议设为
make(chan error, 1000),太小会阻塞生产者,太大浪费内存 - 发送前检查 channel 是否已关闭:
select { case panicCh ,避免 shutdown 阶段 panic - consumer 必须用
for err := range panicCh,不是单次读取
Gin 中替换默认 Recovery 中间件,支持自定义错误类型断言
原生 gin.Recovery() 只返回 500 + 堆栈,无法区分业务异常和系统 panic。要实现分级响应,得自己写中间件并按类型断言。
- panic 值可能是
*AppError、error或原始字符串,需用switch e := err.(type)分支处理 - 对
*AppError提取e.Code作为 HTTP 状态码,e.Message作响应体 - 务必调用
c.Abort(),否则 panic 后仍可能执行后续 handler,导致重复响应或 panic 重入
recover 后禁止继续执行原逻辑,只做清理和上报
recover 不是“重试”或“续跑”,它只是终止 panic 传播、拿回控制权。原函数栈已损坏,继续执行大概率引发二次 panic 或数据不一致。
- 不要在 recover 块里调用
c.Next()或重试业务逻辑 - 只允许做三件事:记录日志(含
debug.Stack())、发错送到监控 channel、释放本地资源(如 close 文件句柄) - 切忌在 defer 里调用
os.Exit()或log.Fatal(),这会让整个进程退出,不是仅终止当前请求
实际部署时,最容易被忽略的是 goroutine 生命周期与 panic 范围的严格绑定——没有“全局 recover”,只有“每个 goroutine 自己负责自己”。一旦漏掉某个异步路径(比如定时任务、消息回调、HTTP 流式响应中的 goroutine),panic 就会直接炸穿服务。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











