2013年2月8日 星期五

淺談 go 語言的defer關鍵字

在寫程式,我們時常犯一種錯誤,就是要了資源,忘了釋放。舉例來說,你 fopen 了檔案,卻沒有 fclose 。你 malloc 一塊記憶體,卻忘了 free 它。 你 acruire 一個 locker,卻忘了 release等等。記憶體管理也許可以藉由GC技術,而減輕許多負擔。但是另外兩種呢?

要怎麼減少犯錯的機會呢?盡量減少fopen和fclose之間的距離,越近越好。最好是fopen完就呼叫fclose,這樣犯錯的可能性就降低很多了。



可是你fopen後馬上fclose,這樣好像也沒有意義。所以怎麼辦呢? go 語言引進了一個機制,類似物件的解構子,只是他是適用於 function。 當一個 function 的敘述是defer 開頭的,他不會立刻執行,他會等到函數結束時才會執行。

來看個簡單的例子。
package main
import "fmt"

func test(){
    fmt.Printf("test start\n")
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%d\n", i)
    }
    fmt.Printf("test end\n")
}
func main(){
    test()
}

test start
test end
4
3
2
1
0

你會發現,它是在印完 "test end" 後,才開始印數字。而導致這個結果都主因就是defer這個關鍵字。

defer 的作用是把後面接的指令 push 到 stack裡,等到 function執行完畢要返回到caller(在執行完return後)時,在從 stack 取出指令來執行。因為1是最先被push進去的,所以他最後被執行。


有了 defer 敘述,我們就可以這樣寫開檔指令


    f, err := os.Open("somefile", os.O_RDWR | os.O_CREATE, 0666)
    if err != nil {
        return
    }

    defer f.Close()
開檔案和關檔案的敘述靠的夠近,這樣就不怕忘記了!!

當然,defer 在 go lang 裡另外一個作用就是和 panic 、 recover函數搭配,改天再寫這個。

這個 blog 也有介紹 defer 、panic、recover的用法
http://blog.golang.org/2010/08/defer-panic-and-recover.html

那個 blog 有個有趣的 defer 例子,程式碼如下。

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


上面那段程式碼 c 的回傳值將是2,這就是為什麼我之前強調執行 defer 敘述發上在 return 後。 至於他為什麼他會這樣設計,主要是為了panic、recover。當函數執行時發生panic時,可以在 defer 的 function 裡乎要recover,並在function修改回傳值。就像下面程式碼所示範的的,當程式有panic時,他會把 err_code 改成1。


package main
import "fmt"

func test(){
    fmt.Printf("test start\n")
    for i := 0; i < 5; i++ {
        defer fmt.Printf("%d\n", i)
    }
    fmt.Printf("test end\n")
}


func test1()(err_code int){
    defer func() {
        if r := recover(); r != nil{
            err_code = 1
        }
    }()
    panic("123")
    return 0
}


func main(){
    ret := test1()
    fmt.Printf("%d\n", ret)
}

沒有留言:

張貼留言