Skip to content

Latest commit

 

History

History

lesson28

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

panic和recover

panic和recover是Go的2个内置函数,用于程序运行期抛出异常(panic的功能)和处理异常(recover的功能)。

panic

引发panic可以是程序显式调用panic函数,也可以是运行期错误,比如数组越界访问,除0等。

  • 定义

    func panic(interface{}) // 入参是一个空interface,无返回值
  • 示例

    panic(12)
    panic("invalid parameter")
    panic(Error("cannot parse"))
  • 如果在函数F里,显式调用了panic或者函数F执行过程中出现运行期错误,那F的执行会终止,接下来会有以下行为依次产生:

    • F里被defer的函数会执行。
    • F的上一级函数,也就是调用F的函数,假设是函数E。对函数E而言,对F的调用就等同于调用了panic,函数E里被defer的函数被执行
    • 如果函数E还有上一级函数,就继续往上,每一级函数里被defer的函数都被执行,直到没有上一级函数。
    • 经过了以上步骤,panic的错误就会被抛出来,整个程序结束。
  • 既然可以用panic来抛运行期异常,那就有相应办法可以捕获异常,让程序正常往下执行。Go通过结合内置函数recover和defer语义,来实现捕获运行期异常。

  • 对于标准Go编译器,有些致命错误是无法被recover捕捉的,比如栈溢出(stack overflow)或者内存超限(out of memory),遇到这种情况程序就会crash。

recover

recover是Go的内置函数,可以捕获panic异常。recover必须结合defer一起使用才能生效。

程序正常执行过程中,没有panic产生,这时调用recover函数会返回nil,除此之外,没有其它任何效果。

如果当前goroutine触发了panic,可以在代码的适当位置调用recover函数捕获异常,让程序继续正常执行,而不是异常终止。

  • 定义

    func recover() interface{} // 返回值是一个空interface,无入参
  • 示例

    package main
    
    import (
    	"fmt"
    )
    
    func a() {
    	defer func() {
    		/*捕获函数a内部的panic*/
    		r := recover()
    		fmt.Println("panic recover", r)
    	}()
    	panic(1)
    }
    
    func main() {
    	defer func() {
    		/*因为函数a的panic已经被函数a内部的recover捕获了
    		所以main里的recover捕获不到异常,r的值是nil*/
    		r := recover()
    		fmt.Println("main recover", r)
    	}()
    	a()
    	fmt.Println("main")
    }

    上例的执行结果是:

    panic recover 1
    main
    main recover <nil>
  • recover在以下几种情况返回nil

    • panic的参数是nil。这种情况recover捕获后,拿到的返回值也是nil。
    • goroutine没有panic产生。没有panic,那当然recover拿到的也就是nil了。
    • recover不是在被defer的函数里面直接调用执行。
  • 一个更复杂的示例

    package main
    
    import "fmt"
    
    func main() {
        f()
        fmt.Println("Returned normally from f.")
    }
    
    func f() {
        defer func() {
            if r := recover(); r != nil {
                fmt.Println("Recovered in f", r)
            }
        }()
        fmt.Println("Calling g.")
        g(0)
        fmt.Println("Returned normally from g.")
    }
    
    func g(i int) {
        if i > 3 {
            fmt.Println("Panicking!")
            panic(fmt.Sprintf("%v", i))
        }
        defer fmt.Println("Defer in g", i)
        fmt.Println("Printing in g", i)
        g(i + 1)
    }

    大家可以下载recover2.go代码,本地运行看看结果是否和预期相符。

References