預習內容

  • defer 的作用有哪些?
  • 多個 defer 的執行順序是怎樣的?
  • defer,return,函式返回值 三者之間的執行順序

defer的作用

go中的defer延遲函式,一般是用於釋放資源或者收尾工作

由於defer是具有延遲特性且執行動作是在函式return之後,因此作為資源釋放作用再好不過。

  • 典型例子:釋放鎖、關閉檔案、關閉連結
// 釋放鎖
func getValue() {
s.Lock()
defer s.Unlock()
...
} // 關閉檔案
func read() {
f, err := os.OpenFile("filename", os.O_RDWR, os.ModePerm)
defer f.Close()
...
} // 關閉連結
func connect() {
resp, err := grequests.Get(s.cfg.GateApiCrossMarginUrl, nil)
defer resp.Close()
...
} // 收尾工作,defer 同時也是函式,可以做很多收尾相關工作
func closeConnection() {
...
defer func() {
file.close()
close(readChan)
}
...
}
  • 還有作用就是捕獲 panic,這個功能在defer裡也是典型用法
func sendChan() {
// 此處捕獲 panic 進行 recover 防止程式崩潰
defer func() {
if ok := recover(); ok != nil {
fmt.Println("recover")
}
}()
// 向已經關閉的chan傳送資料,此處會引起 panic
dataChan <- "message"
...
}

defer 釋放資源『避坑指南』

資源釋放動作一定緊跟資源使用(開啟、連線)語句,不然defer可能不會被執行到,導致記憶體洩露

// 關閉檔案
func read() error {
f1, err := os.OpenFile("filename", os.O_RDWR, os.ModePerm)
if err != nil {
return err
}
// 此時,defer還沒執行到,提前return了,無效defer導致記憶體洩露
defer f1.Close() // 正確用法,緊跟資源使用語句
f2, err := os.OpenFile("filename", os.O_RDWR, os.ModePerm)
defer f2.Close()
if err != nil {
return err
}
}

defer 的呼叫順序

牆裂建議不要先看後面的介紹做下面的題目,此處先跳過介紹defer呼叫順序,看看下面比較典型的對defer順序判斷,你能看出來幾個?

func deferFunc1(i int) (t int) {
t = i
defer func() {
t += 1
}()
return t
} func deferFunc2(i int) int {
t := i
defer func() {
t += 1
}()
return t
} func deferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 1
} func deferFunc4() (t int) {
defer func(i int) {
fmt.Println(i)
fmt.Println(t)
}(t)
t = 0
return 1
} func ExecDeferFunc() {
// 猜猜下面輸出的內容和順序
fmt.Println(deferFunc1(1))
fmt.Println(deferFunc2(1))
fmt.Println(deferFunc3(1))
deferFunc4()
}

猜想結果可能是:2,2,1,0,0 或者 2,2,1,1,1 ?

估計比較模糊的地方應該是函式返回值 和 return value(函式返回值)關係不明確,還有就是對defer產生作用的時機不明確

正文開始!

再次強調defer是延遲函式,執行動作在return之後,defer相當於是將執行動作壓入棧中,越是後面的defer越是先執行,執行順序是LIFO(後進先出)

特別指出:有函式返回值的則return將結果寫入返回值,defer進行收尾,可以看做 return最先執行,然後return將結果存入返回值,最後defer執行

那麼基於剛剛的介紹再回頭去看得到的『實際結果是:2,1,2,0,1』

複習內容

  • defer 用於資源釋放和收尾工作
  • 多個 defer 呼叫順序是 LIFO(後入先出),defer後的操作可以理解為壓入棧中
  • defer,return,return value(函式返回值) 執行順序:首先return,其次return value,最後defer。defer可以修改函式最終返回值。