协程池中recover必须在每个worker goroutine内部的for循环最外层用defer包裹,否则panic会导致worker静默退出;recover需配合日志或监控上报,且recover后应跳过当前task继续消费,不可放在select内或task执行后。

协程池里recover必须每个worker自己加
协程池中单个worker goroutine崩溃,不会影响其他worker,但也不会被池外的recover捕获。你在NewPool函数里或main函数里加defer recover()完全无效——它只对当前goroutine起作用,而worker是独立启动的。
正确做法是在每个worker内部的循环逻辑前,用defer包裹recover,且必须放在for循环最外层,否则panic后worker直接退出,无法继续消费任务。
- 必须写在
worker函数体开头,不是池初始化时 - recover要配合
log.Printf或监控上报,不能只recover不记录 - recover后不应继续执行原任务逻辑(状态可能已损坏),而是跳过该task,继续取下一个
recover位置错了会导致worker静默退出
常见错误是把recover写在select块内部、或者放在task.Execute()之后——这样panic发生时,recover根本没机会执行,worker会直接终止,任务队列里的后续任务全部卡住。
正确结构必须是:defer → for循环 → select监听channel → 执行task。中间任何一层漏掉defer,panic就逃逸。
立即学习“go语言免费学习笔记(深入)”;
OpenClaw 基础设施心跳监控与自动恢复:部署/卸载心跳守护进程,配置主机监控(Gateway、磁盘、内存、CPU、进程),设置飞书告警,自动重启故障Gateway。适用关键词:心跳监控、heartbeat daemon、服务器告警、自动重启、健康检查、auto recovery。
- 错误写法:
select { case t := - 正确写法:
func(p *Pool) worker(ctx context.Context) { defer func(){if r:=recover();r!=nil{...}}(); for {...} } - 如果
task.Execute()里有阻塞调用(如HTTP请求),需额外设timeout,否则panic前就卡死
recover捕获后别吞异常,要联动熔断和日志
单纯recover让worker不死,不代表服务健康。高频panic往往意味着下游超时、数据污染或内存泄漏,此时需要:
- 每次recover都调用熔断器的
MarkFailed()(如gobreaker.NewCircuitBreaker) - 记录
panic值和堆栈(用debug.PrintStack()或runtime.Stack()) - 触发
srv.Shutdown()前检查panic频率,比如1分钟内超5次就主动降级
注意:recover返回的是interface{},直接fmt.Printf("%v", r)可能打印不全,建议转成fmt.Sprintf("%+v", r)并附上runtime.Caller(1)定位出问题的worker代码行。
第三方库的recover封装不等于自动兜底
像ants或pond这类协程池库,部分版本默认不启用recover;即使启用了,也只做基础捕获,不包含熔断、指标上报或优雅退出逻辑。
- 用
ants.NewPool(10, ants.WithPanicHandler(...))时,handler里仍要手动写日志+计数 -
pond.NewThreadPool默认无panic处理,必须传pond.Options{PanicHandler: ...} - 不要依赖框架“自动恢复”,所有生产级协程池都应显式验证recover是否生效(可故意在task里
panic("test")测试)
最容易被忽略的是:recover只能防止单个worker崩溃,但无法修复已泄漏的goroutine或未关闭的文件句柄。每次recover后,务必检查资源清理逻辑是否完整——比如task是否实现了Cleanup()方法并在defer中调用。

















