天天看点

Defer, Panic, and Recoverdeferpanicrecover

原文链接http://blog.golang.org/defer-panic-and-recover
  • defer
    • 1 A deferred functions arguments are evaluated when the defer statement is evaluateddefer函数的参数在函数表达式解析的时候就求值
    • 2 Deferred function calls are executed in Last In First Out order after_the surrounding function returnsdefer函数遵循先入后出的规则因为其本身是压栈的方式
    • 3 Deferred functions may read and assign to the returning functions named return valuesdefer函数可以用来操作返回值
    • 4 defer函数中除参数以外参数以值传递方式入栈其余变量都是引用的形式与一般函数一致
  • panic
  • recover

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
}
           

如果os.Create()失败了,将会导致source file没有关闭的情况。如果你使用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的使用过程中,需要注意几条规则,就能防止误用:

(1) A deferred function’s arguments are evaluated when the defer statement is evaluated.(defer函数的参数在函数表达式解析的时候就求值)

在下面的例子中,i在解析defer函数的时候就求值,所以最后会输出0:

func a() {
    i := 
    defer fmt.Println(i)
    i++
    return
}
           

(2) Deferred function calls are executed in Last In First Out order after_the surrounding function returns.(defer函数遵循先入后出的规则,因为其本身是压栈的方式)

下面的代码将输出“3210”

func b() {
    for i :=; i <; i++ {
        defer fmt.Print(i)
    }
}
           

(3) Deferred functions may read and assign to the returning function’s named return values.(defer函数可以用来操作返回值)

下面的代码将返回2

func c() (i int) {
    defer func() { i++ }()
    return
}
           

这个特点非常方便的用来修改返回的error

上面的几条规则非常简单,还有一个比较容易出错的点,本质和第三条一致。在原博的基础上我又添加了一条:

(4) defer函数中除参数以外(参数以值传递方式入栈),其余变量都是引用的形式,与一般函数一致

所以下面的代码会诡异的输出“4444”

package main

import "fmt"

func main() {
    for i :=; i <; i++ {
        defer func() {
            fmt.Print(i)
        }()
    }
}
           

panic

panic是一个产生运行时恐慌的内建函数,你可以手动调用panic来触发,也有可能是运行时错误导致的自动panic。

需要注意的几点是:

  • panic产生后每个递归的defer的方法依次出栈执行。panic从子程序往父程序传递,直到当前goroutine返回。
  • go库函数的惯例一般都是API内部panic由recover转化为显式error返回

recover

recover是内奸函数用来捕捉panic,并阻止了panic的逐级上传,和字面意思蛮像的其实。

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)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i +)
}
           

上面的代码将会输出:

Calling g.
Printing in g 
Printing in g 
Printing in g 
Printing in g 
Panicking!
Defer in g 
Defer in g 
Defer in g 
Defer in g 
Recovered in f 
Returned normally from f.
           

如果我们将f函数中的recover方法移除,goroutine将会由此结束,从而输出:

Calling g.
Printing in g 
Printing in g 
Printing in g 
Printing in g 
Panicking!
Defer in g 
Defer in g 
Defer in g 
Defer in g 
panic: 

panic PC=x2a9cd8
[stack trace omitted]
           

json包中有很多利用panic和recover的例子,遇到不符合json规则的文本,其内部会触发panic,并在高层代码中捕捉panic将其改成合适的error返回值。(参与代码decode.go)

含有一些常见的defer的使用案例,如锁的加锁解锁:

mu.Lock()
defer mu.Unlock()
           

输出footer:

printHeader()
defer printFooter()
           

凡此种种,碰到问题再讨论吧…

by Andrew Gerrand(translator: xiaohu)