GO成神之路: Go异常处理(三)|Go主题月

1,121 阅读2分钟

Go异常处理(二)中我们介绍了gopanic得代码实现,其中提到了mcall用来回到正常得函数调用,也就是继续调用defer链中的一个defer,或者理解为当前gopanic函数已经执行完成了,然后模拟return退出gopanic函数。

另外我们也提到了fatalpanic函数,当mcall函数不能被调用时,在方法得最后会调用fatalpanic

代码精简如下:

// gopanic接收一个参数,这个参数就是通过panic函数传递的参数
func gopanic(e interface{}) {
    // ...       
    // 当发生panic时,会立即处理当前g的_defer队列
    for {
        //调用mcall函数,相当于return
        mcall(recovery)
    }
    // ... 
    // 致命错误,调用exit()退出
    fatalpanic(gp._panic) // should not return
    // ... 
}

gopanic函数中,如果所有得defer中都没有使用recover来处理panic,则在函数的最后会调用fatalpanic函数来结束当前程序的运行。

为什么会结束程序的运行?

从设计角度来说:如果存在未处理的异常时,应该结束程序;从代码实现来说:是因为它调用exit函数。

runtime.fatalpanic(msgs *_panic)

func fatalpanic(msgs *_panic) {
    // ...
    /// 通过调用exit函数来结束程序
    systemstack(func() {
        // 无条件退出程序
        exit(2)
    })
}

所以为了防止程序崩溃,我们应该尽量在函数中使用recover函数来处理panic,但在正常的开发中我们也不可能在每个函数中定义defer,因为只要存在defer的定义,就会消耗一部分性能来处理defer链。

但时我们可以在一个功能开始的位置定义defer来处理panic防止程序崩溃:

func test(){
    defer func() {
            if err := recover(); err != nil {
                    log.Println(err)
            }
    }()
    // 进行一些其它函数调用
    testA()
    testB()
    testC()        
    panic("error")
}

这就要求我们明确当前函数的后续执行有没有可能放生panic。

我们应该将recover()反正距离panic最近的函数中,这样可以保证错误不会被传递的太远,这不是为了提高性能,而是为了让我们的业务在发现致命问题时最快的被修正。