go中的defer、panic和recover

defer:

在看go标准库的时候,不时能看到函数中有一些defer语句,一开始挺疑惑,后来查了下,发现这是内置的关键字,用来确保某个函数调用 在当前函数执行结束时 执行:

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

    dstFile, err := os.Create(dst)
    if err != nil {
        return
    }
    defer dstFile.Close()

    return io.Copy(dstFile, srcFile)
}

上面是个常见的例子,执行完copyFile操作之后,打开的源文件和目标文件都能确保被释放。

如果不使用defer的话,代码可能是这样:

    ...
    written, err = io.Copy(dst, src)
    dstFile.Close()
    srcFile.Close()
    return

这种情况下,如果执行os.create()方法报错,scrFile还没来得及Close(),函数就因为err!=nil返回了。

defer也常用于释放数据库连接,或释放sync.mutex的互斥锁。

//释放互斥锁
mu.Lock()
defer mu.Unlock()
...
//关闭response body
resp, err := http.Get("http://example.com/")
if err != nil {
	// handle error
}
defer resp.Body.Close()
...

一个函数内部可以defer多个待执行函数,这些待执行函数会以栈的方式保存在一个list中,后进先出地执行。

今天看到一篇官方post:Defer, Panic, and Recover

发现defer除了调用顺序外,还有一些需要注意的地方:

1.如果defer的待执行函数带参,比如(i int)。参数的值为执行到该defer时i的值:

func a() {
    i := 0
	i++
	defer fmt.Println(i)  //此时i值为1,随后该函数执行时i为1
    defer fmt.Println("second")
	defer fmt.Println("first")
    i++
    return
}
func main(){
    a()
}
//打印出:
first
second
1

2.如果defer所在的函数声明了返回,defer的待执行函数可以读取并修改这个返回值:

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

如上,函数c声明了返回,其内defer的待执行函数读取并修改了i。c()返回的值为2。

panic:

go中有个内置的函数panic。panic用来中止当前的执行流。如果函数F在执行期间调用了panic函数,则函数F()立即中止,defer的待执行函数照常执行,然后F返回给调用F的那个函数。此时F也作为一个panic函数,中止当前函数,一直沿着调用栈直到这个goroutine的顶层,直到该goroutine返回。也就是说,这个程序崩了。

panic可以直接调用,也可以由运行时错误产生。

recover:

对于被panic的goroutine,go提供了一个recover内置函数,用来恢复被panic中止的goroutine。recover函数只对被panic中止的goroutine有用,正常情况下调用recover只会返回nil。

下面引用它的demo:

package main
import "fmt"
func main() {
    f()
    fmt.Println("Returned normally from f.")
}
//函数f调用函数g,但是defer了一个func函数用于检测是否发现panic
//func函数始终执行recover(),确保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.")
}
//g函数内部调用panic
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)
}

recover可以防止程序意外崩坏,如果把f函数中的defer func去掉,会打印出:

panic: 4
 
panic PC=0x2a9cd8
[stack trace omitted]

猜你喜欢

转载自blog.csdn.net/weixin_36094484/article/details/82317587