Golang---Defer, Panic, and Recover

来源:互联网 发布:超级肉食男孩 mac 编辑:程序博客网 时间:2024/06/05 10:45

Go 语言中保留有传统的控制流程,比如:if, for, switch,甚至连 goto 都有。另外 goroutine 语法也可以实现面向过程的多线程开发。在这里我们将要讨论一些同其它语言不相同的语句:defer, panic, 和 recover

defer 语句可以将一个函数放入一个链表。当调用这个 defer 语句的函数返回时,保存在链表中的所有函数将会被调用。一般情况下,defer 语句用来进行简单的各种清理工作。

例如,在一个函数中,我们打开了两个文件,并且拷贝一个文件的内容到另一个文件中:

func CopyFile(dstName, srcName string) (written int64, err error) {    src, err := os.Open(srcName)    if err != nil {        return    }    dst, err := os.Create(dstName)    if err != nil {        return    }    written, err = io.Copy(dst, src)    dst.Close()    src.Close()    return}

上述函数可以正常运行,但是存在BUG。如果在调用 os.Create 时发生异常,程序这个时候并没有关闭原始被拷贝的文件。当然,我们可以在第二个 return 语句之前,添加 src.Close,但是如果程序变化复杂,修改起来就没这样容易了。通过使用 defer 语句, 能够保证文件总是被关闭

func CopyFile(dstName, srcName string) (written int64, err error) {    src, err := os.Open(srcName)    if err != nil {        return    }    defer src.Close()    dst, err := os.Create(dstName)    if err != nil {        return    }    defer dst.Close()    return io.Copy(dst, src)}

defer 语句可以保证打开打开文档之后,能够正确的关闭文件,而不去考虑在之后的的逻辑中有多少个 return 语句。

defer 语句的行为直观明了,并且有三个简单的特性:

  • deferred 函数参数初始化的时机为 defer 语句声明时

    在下面的例子中,当 Println 被调用时,i 当前的值已经传递到函数中,所以值为 0

    func a() {        i := 0        defer fmt.Println(i)        i++        return    }
  • 当外围函数返回时, deferred 函数按照 后进先出[LIFO] 的顺序执行
    下面的例子将会打印 3210
    func b() {        for i := 0; i < 4; i++ {            defer fmt.Print(i)        }    }
  • defered 函数可以读取外围函数的返回值,并对其进行赋值操作
    下面的例子中, defered 函数增加了外围函数的返回值,所以,最后的返回结果为 2
    func c() (i int) {        defer func() { i++ }()        return 1    }
这对于改变程序返回错误是非常方便的。在下面会举一个简单的例子。

Panic 是内置函数,终止当前的流程,使之开始变得 panicking。例如,当函数 F 调用 panicF 将会停止执行,任何 deferred 函数将会正常执行,之后 F 返回到调用它的函数。对于调用者而言,F 就相当于一个 panic。这一过程持续向上直到程序崩溃。 Panics 可以通过直接调用 panic 关键字实现,也可以通过 runtime errors 发生,例如数组越界。

Recover 也是内置函数,用来恢复遇到 panicgoroutineRecover 仅仅在 deferred 函数中才有意义。如果,在正常的函数中调用,recover 将会返回 nil 并且没有其它任何效果。如果当前的 goroutine 产生了 panic, recover 将会捕获传递给 panic 的数据,并且恢复到正常的执行状态。

下面的程序将会说明 panic defer recover 的机制。

package mainimport "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)}

函数 g 根据 i 的值执行不同的动作,当 i 大于 3 时会主动产生 panic。 函数 f 通过 deferred 函数,使用 recover 捕获异常,如果不为空,则打印恢复的数据。在继续读以下内容之前,试着想想输出的内容。

程序的输出结果为:

Calling g.Printing in g 0Printing in g 1Printing in g 2Printing in g 3Panicking!Defer in g 3Defer in g 2Defer in g 1Defer in g 0Recovered in f 4Returned normally from f.

如果我们将函数 f 中的 deferred 函数去掉,panic 将不会被捕获,整个程序将会崩溃。修改后的程序如下:

Calling g.Printing in g 0Printing in g 1Printing in g 2Printing in g 3Panicking!Defer in g 3Defer in g 2Defer in g 1Defer in g 0panic: 4panic PC=0x2a9cd8[stack trace omitted]

可以从 Go 的标准库 json package 中获取 panicrecover 的真实例子。

Go 语言库的方便之处就在于,即使库种使用了 panic ,外部 API 仍然会返回明确的出错信息,并不会引起 panic

defer 的其它应用(不是之前提到的 file.Close)包括释放锁:

mu.Lock()defer mu.Unlock()

打印页脚

printHeader()defer printFooter()

等等…

总而言之,defer 语句(结合 panicrecover 与否)提供一种不同寻常强大的控制机制。它可以通过不同多样的建模实现其它语言的特殊逻辑控制。不妨试一试哦。

By Andrew Gerrand

原文网址

译者注:defer 在某种程序上更像finally, panic 在某种程度上更像 throw exception, recover 在某种程度上更像 catch

0 0
原创粉丝点击