在Go中,必须在每个goroutine内部使用defer+recover来捕获panic,因为panic不会跨goroutine传播。主goroutine的recover无法处理子goroutine的panic,否则会导致程序崩溃或资源泄漏。正确做法是在启动goroutine时立即设置defer recover,例如通过safeGo封装函数,在其中添加recover机制并记录日志,从而保证单个goroutine的错误不会影响整个程序。这一模式广泛应用于Web服务、后台任务等高可靠性场景。
在Go语言中,让程序在goroutine里安全地捕获panic,关键在于每个可能出错的goroutine内部必须有自己的defer和recover机制。主流程或外部的recover是无法捕获到子goroutine内部的panic的。
panic的作用范围仅限于它发生的那个goroutine。当你在一个独立的goroutine里运行代码时,如果那里发生了panic,而你没有在其中设置recover,那么这个panic只会终止该goroutine,并且不会被外面的任何recover捕获,这通常会导致难以察觉的错误和资源泄漏。
一个常见的误区是认为在main函数或调用处使用defer+recover就能兜住所有goroutine的panic,这是不正确的。请看下面的例子:
func main() {正确的方法是在启动goroutine的函数体最外层就设置好defer+recover,形成一个保护层。这样无论goroutine内部哪一层代码触发了panic,都会被这个顶层的recover捕获。
实现步骤如下:
go关键字启动的匿名函数或目标函数的开头,立即定义一个defer函数defer函数内部调用recover()
recover()的返回值,如果不为nil,说明发生了panic,可以进行日志记录、发送错误信号等操作
// 模拟可能会出错的业务逻辑在这个例子中,虽然riskyOperation函数因为除零错误触发了panic,但由于其所在的goroutine拥有自己的recover机制,整个程序不会崩溃,main函数也能继续正常执行。
这种模式在构建健壮的服务端应用时非常有用,尤其是在处理网络请求、定时任务或消息队列消费者时。
使用safeGo(riskyOperation)就可以安全地启动任何可能panic的函数。基本上就这些,核心就是“谁的孩子谁抱走”,每个goroutine要为自己负责。