Go函数--panic和recover

1 基本概念

panic(宕机) 和 recover(恢复)是Go语言的两个内置函数,这两个内置函数用来处理Go的运行时错误(runtime errors)。panic 用来主动抛出异常,recover 用来捕获panic 抛出的异常。

panic 和 recover的函数原型如下:

panic(i interface{})
recover() interface{}

2 宕机(panic) — 程序终止运行

引发panic有两种情况:一种是程序主动调用panic()函数,另一种是程序产生运行时错误,由运行时检测并抛出。

示例1:程序主动调用panic,触发宕机,让程序崩溃。

func funcA() {
    fmt.Println("func A")
}

func funcB() {
    panic("panic in B")
}

func funcC() {
    fmt.Println("func C")
}
func main() {
    funcA()
    funcB()
    funcC()
}

运行结果:

func A
panic: panic in B

goroutine 1 [running]:
main.funcB(...)
	/home/wangxm/go_work/src/chapter05/demo.go:12
main.main()
	/home/wangxm/go_work/src/chapter05/demo.go:20 +0x96
exit status 2

《代码分析》当在funcB中主动调用了panic 函数后,程序发生宕机直接退出了,同时输出了堆栈和goroutine相关信息,这让我们可以看到错误发生的位置。

##当panic()触发的宕机发生时,panic后面的代码将不会被执行,但是在panic()函数前面的已经执行过的defer语句依然会在宕机发生时执行defer中的延迟函数。

示例2:在宕机时触发defer语句延迟函数的执行。

func funcA() {
    fmt.Println("func A")
}

func funcB() {
    panic("panic in B")
}

func funcC() {
    fmt.Println("func C")
}

func funcD() {
    fmt.Println("func D")
}

func main() {
    defer funcA()
    defer funcC()
    
    fmt.Println("this is main")
    
    funcB()
    
    defer funcD()
}

运行结果:

this is main
func C
func A
panic: panic in B

goroutine 1 [running]:
main.funcB(...)
	/home/wangxm/go_work/src/chapter05/demo.go:12
main.main()
	/home/wangxm/go_work/src/chapter05/demo.go:29 +0xca
exit status 2

《代码分析》从运行结果可以看出,当程序流程正常执行到funcB()函数的panic语句时,在panic()函数被执行前,defer语句会优先被执行,defer语句的执行顺序是先进后出,所以funcC()延迟函数先执行,funcA()后执行,当所有已注册的defer语句都执行完毕,才会执行panic()函数,触发宕机,程序崩溃退出,因此程序流程执行不到funcD()函数。

<提示> 发生panic后,程序会从调用panic的函数位置或发生panic的地方立即返回,逐层向上执行函数的defer语句,然后逐层打印函数调用堆栈信息,直到被recover捕获或运行到最外层函数而退出。如本例中,程序从funcB()函数返回到上层的main()函数中,然后执行已注册的defer语句的延迟函数,最后从main函数中退出,并且打印了退出状态码的值为2。

##什么情况下主动调用panic() 函数抛出异常?

一般有下面两种情况:

(1)程序遇到了无法正常执行下去的错误,主动调用panic 函数结束程序运行。

(2)在调试程序时,通过主动调用panic 函数实现快速退出,panic打印出的堆栈信息能够更快地定位错误。

显然,我们是不希望程序崩溃的。那么,当程序在运行过程中突然发生了异常时,怎样“捕获”这个异常并处理这个异常避免程序崩溃呢?

3 恢复(recover) — 防止程序崩溃

recover()函数用来捕获或者说是拦截panic的,阻止panic继续向上层传递。无论是主动调用panic()函数触发的宕机还是程序在运行过程中由Runtime层抛出的异常,都可以配合defer 和 recover 实现异常捕获和恢复,让代码在发生panic后能够继续执行。

<提示> 在其他编程语言中,如Java,宕机往往以异常的形式存在。底层抛出异常,上层逻辑通过try...catch...机制捕获异常并处理,没有被捕获到的严重异常会导致程序崩溃,捕获的异常可以被处理,让代码可以继续执行。

Go语言没有异常系统,其使用panic触发宕机类似于其他语言的抛出异常,而recover的宕机恢复机制就对应try...catch机制。

##】关于recover()函数的说明

recover()函数被调用后,会返回一个 interface{} 接口类型的返回值,如果返回值等于nil,说明没有触发panic;反之,说明程序发生了panic,就应该采取相应的措施。

示例1:使用recover捕获panic异常,恢复程序的运行。

func funcA() {
    fmt.Println("func A")
}

func funcB() {
    defer func(){
        //捕获panic,并恢复程序使其继续运行
        if err := recover(); err != nil {
            fmt.Println("recover in funcB")
        }
    }()

    panic("panic in B")  //主动抛出异常
}

func funcC() {
    fmt.Println("func C")
}

func funcD() {
    fmt.Println("func D")
}

func main() {
    defer funcA()
    defer funcC()
    
    fmt.Println("this is main")
    
    funcB()
    
    defer funcD()
}

运行结果:

this is main
recover in funcB
func D
func C
func A

《代码分析》当recover捕获到panic时,不会造成整个进程的崩溃,它会从触发panic的位置退出当前函数,然后继续执行后续代码。

##】recover()的使用注意事项

  • recover 必须搭配defer 语句使用,并且recover()函数必须在延迟函数内被调用执行才能正常工作。
  • defer一定要在可能引发panic的语句之前定义。
func funcB() {
    /*defer func(){
        //捕获异常,并恢复程序使其继续运行
        if err := recover(); err != nil {
            fmt.Println("recover in funcB")
        }
    }()*/
    defer recover()      //捕获会失败

    panic("panic in B")  //主动抛出异常
}

4 panic和recover的关系

panic 和 defer语句的组合有如下几个特性:

(1)有panic,没recover,程序宕机。

(2)有panic,也有recover,程序不会宕机。执行完对应的defer后,从宕机点退出所在函数返回到主调函数中,继续执行后续代码。

<提示>

  • 在panic触发的defer语句内,可以继续使用panic,进一步将错误外抛直至程序整体崩溃。
  • 如果想在捕获panic时设置当前函数的返回值,可以对返回值使用命名返回值方式直接进行设置。

5 总结

Go语言提供了两种错误处理的方式,一种是借助 panic 和 recover 的抛出捕获机制,另一种是使用error接口错误类型。在实际的开发工作中,可以根据实际需求选择合适的错误处理方式。

参考

《Go语言从入门到进阶实战(视频教学版)》

《Go语言核心编程》

《Go并发编程实战(第2版)》

《Go语言学习笔记》

 

你可能感兴趣的:(#,Go语言学习笔记,golang,Go函数)