『go成長之路』 defer 作用、典型用法以及多個defer呼叫順序,附加defer避坑點,拿來吧你

亦一銀河發表於2021-08-11

預習內容

  • 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可以修改函式最終返回值。

相關文章