defer、panic 和 recover
三者的关系大概如下:panic内置函数停止当前goroutine的正常执行,当函数F调用panic时,函数F的正常执行被立即停止,然后运行所有在F函数中的defer函数,然后F返回到调用他的函数对于调用者G,F函数的行为就像panic一样,终止G的执行并运行G中所defer函数,此过程会一直继续执行到goroutine所有的函数。panic可以通过内置的recover来捕获。
defer 被调用的时候会调用 runtime.deferproc 定义一个延迟调用对象,然后在函数结束前,调用 runtime.deferreturn 来完成 defer 定义的函数的调用。defer结构体如下:
1 | type _defer struct { |
defer后面接的代码,在函数返回前才会被执行,例子:
1 | func f() (result int) { |
以上值得注意的一点是,return XXX语句并不是一条原子语句,加上defer的定义之后,其实可以替换成以下内容:
1 | 返回值 = XXX |
除非在执行 defer前就直接 return 了,那可以跳过defer。
还有值得注意的一点是,defer虽然是动态的,但是defer函数的参数值,是在申明defer时确定下来的,即如果使用某些值返回时,defer只认最开始被赋的值。对于这点,可以通过传入匿名函数来解决,例如:
1 | func main() { |
1 | func main() { |
panic的作用是中止当前程序,在被执行到时,直接使当前程序崩溃,然后返回。值得注意的一点是,当panic和defer同时存在时,在panic执行前,会先去执行defer的内容,随后再使程序崩溃,此时defer中的内容还是能够执行下去。defer 表达式的函数如果定义在 panic 后面,该函数在 panic 后就无法被执行到。
recover是 defer专属的对应函数,如果F的defer中无recover捕获,则将panic抛到G中,G函数会立刻终止,不会执行G函数内后面的内容,但不会立刻return,而调用G的defer…以此类推
F中出现panic时,F函数会立刻终止,不会执行F函数内panic后面的内容,但不会立刻return,而是调用F的defer,如果F的defer中有recover捕获,则F在执行完defer后正常返回,调用函数F的函数G继续正常执行,如果一直没有recover,抛出的panic到当前goroutine最上层函数时,程序直接异常终止。
1 | func G() { |
recover都是在当前的goroutine里进行捕获的,这就是说,对于创建goroutine的外层函数,如果goroutine内部发生panic并且内部没有用recover,外层函数是无法用recover来捕获的,这样会造成程序崩溃。recover返回的是interface{}类型而不是go中的 error 类型,如果外层函数需要调用err.Error(),会编译错误,也可能会在执行时panic。
应注意不要将 panic 返回给调用方,调用方关心的是错误而不是异常,将 panic 转换为 error,优雅处理异常
1 | // 避免 |
1 | // 建议 |