天天看點

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)