go 學習筆記之解讀什麼是defer延遲函數

Go 語言中有個 defer 關鍵字,常用於實現延遲函數

來保證關鍵代碼的最終執行,常言道: "未雨綢繆方可有備無患".

延遲函數就是這麼一種機制,無論程序是正常返回還是異常報錯,只要存在延遲函數都能保證這部分關鍵邏輯最終執行,所以用來做些資源清理等操作再合適不過了.

go 學習筆記之解讀什麼是defer延遲函數

出入成雙有始有終

日常開發編程中,有些操作總是成雙成對出現的,有開始就有結束,有打開就要關閉,還有一些連續依賴關係等等.

一般來說,我們需要控制結束語句,在合適的位置和時機控制結束語句,手動保證整個程序有始有終,不遺漏清理收尾操作.

最常見的拷貝文件操作大致流程如下:

  • 打開源文件
go 學習筆記之解讀什麼是defer延遲函數

  • 創建目標文件
go 學習筆記之解讀什麼是defer延遲函數

  • 拷貝源文件到目標文件
io.Copy(dstFile, srcFile)
  1. 關閉目標文件
dstFile.Close() 

srcFile.Close()
  1. 關閉源文件
srcFile.Close()

值得注意的是: 這種拷貝文件的操作需要特別注意操作順序而且也不要忘記釋放資源,比如先打開再關閉等等!

go 學習筆記之解讀什麼是defer延遲函數

「雪之夢技術驛站」: 上述代碼邏輯還是清晰簡單的,可能不會忘記釋放資源也能保證操作順序,但是如果邏輯代碼比較複雜的情況,這時候就有一定的實現難度了!

可能是為了簡化類似代碼的邏輯,Go 語言引入了 defer 關鍵字,創造了"延遲函數"的概念.

  • 無 defer 的文件拷貝
go 學習筆記之解讀什麼是defer延遲函數

  • 有 defer 的文件拷貝
go 學習筆記之解讀什麼是defer延遲函數

上述示例代碼簡單展示了 defer 關鍵字的基本使用方式,顯著的好處在於 Open/Close 是一對操作,不會因為寫到最後而忘記 Close 操作,而且連續依賴時也能正常保證延遲時機.

簡而言之,如果函數內部存在連續依賴關係,也就是說創建順序是 A->B->C 而銷燬順序是 C->B->A.這時候使用 defer 關鍵字最合適不過.

懶人福音延遲函數

官方文檔相關表述見 Defer statements[1]

如果沒有 defer 延遲函數前,普通函數正常運行:

go 學習筆記之解讀什麼是defer延遲函數

當添加 defer 關鍵字實現延遲後,原來的 1 被推遲到 2 後面而不是之前的 1 2 順序.

go 學習筆記之解讀什麼是defer延遲函數

如果存在多個 defer 關鍵字,執行順序可想而知,越往後的越先執行,這樣才能保證按照依賴順序依次釋放資源.

go 學習筆記之解讀什麼是defer延遲函數

相信你已經明白了多個 defer 語句的執行順序,那就測試一下吧!

go 學習筆記之解讀什麼是defer延遲函數

初步認識了 defer 延遲函數的使用情況後,我們再結合文檔詳細解讀一下相關定義.

  • 英文原版文檔

A "defer" statement invokes a function whose execution is deferred to

the moment the surrounding function returns,either because the surrounding function executed a return statement,reached the end of its function body,or because the corresponding goroutine is panicking.

  • 中文翻譯文檔

"defer"語句調用一個函數,該函數的執行被推遲到周圍函數返回的那一刻,這是因為周圍函數執行了一個return語句,到達了函數體的末尾,或者是因為相應的協程正在驚慌.

具體來說,延遲函數的執行時機大概分為三種情況:

周圍函數執行 return

because the surrounding function executed a return statement

return 後面的 t.Log(4) 語句自然是不會運行的,程序最終輸出結果為 3 2 1 說明了 defer 語句會在周圍函數執行 return 前依次逆序執行.

go 學習筆記之解讀什麼是defer延遲函數

周圍函數到達函數體

reached the end of its function body

周圍函數的函數體運行到結尾前逆序執行多個 defer 語句,即先輸出 3 後依次輸出 2 1. 最終函數的輸出結果是 3 2 1 ,也就說是沒有 return 聲明也能保證結束前執行完 defer 延遲函數.

go 學習筆記之解讀什麼是defer延遲函數

當前協程正驚慌失措

because the corresponding goroutine is panicking

周圍函數萬一發生 panic 時也會先運行前面已經定義好的 defer 語句,而 panic 後續代碼因為沒有特殊處理,所以程序崩潰了也就無法運行.

函數的最終輸出結果是 3 2 1 panic ,如此看來 defer 延遲函數還是非常盡忠職守的,雖然心裡很慌但還是能保證老弱病殘先行撤退!

go 學習筆記之解讀什麼是defer延遲函數

通過解讀 defer 延遲函數的定義以及相關示例,相信已經講清楚什麼是 defer 延遲函數了吧?

簡單地說,延遲函數就是一種未雨綢繆的規劃機制,幫助開發者編程程序時及時做好收尾善後工作,提前做好預案以準備隨時應對各種情況.

  • 當週圍函數正常執行到到達函數體結尾時,如果發現存在延遲函數自然會逆序執行延遲函數.
  • 當週圍函數正常執行遇到 return 語句準備返回給調用者時,存在延遲函數時也會執行,同樣滿足善後清理的需求.
  • 當週圍函數異常運行不小心 panic 驚慌失措時,程序存在延遲函數也不會忘記執行,提前做好預案發揮了作用.

所以不論是正常運行還是異常運行,提前做好預案總是沒錯的,基本上可以保證萬無一失,所以不妨考慮考慮 defer 延遲函數?

go 學習筆記之解讀什麼是defer延遲函數

go-error-about-lovely.png

延遲函數應用場景

基本上成雙成對的操作都可以使用延遲函數,尤其是申請的資源前後存在依賴關係時更應該使用 defer 關鍵字來簡化處理邏輯.

下面舉兩個常見例子來說明延遲函數的應用場景.

  • Open/Close

文件操作一般會涉及到打開和開閉操作,尤其是文件之間拷貝操作更是有著嚴格的順序,只需要按照申請資源的順序緊跟著defer 就可以滿足資源釋放操作.

go 學習筆記之解讀什麼是defer延遲函數

  • Lock/Unlock

鎖的申請和釋放是保證同步的一種重要機制,需要申請多個鎖資源時可能存在依賴關係,不妨嘗試一下延遲函數!

go 學習筆記之解讀什麼是defer延遲函數

總結以及下節預告

defer 延遲函數是保障關鍵邏輯正常運行的一種機制,如果存在多個延遲函數的話,一般會按照

逆序的順序運行,類似於棧結構.

延遲函數的運行時機一般有三種情況:

  • 周圍函數遇到返回時
go 學習筆記之解讀什麼是defer延遲函數

  • 周圍函數函數體結尾處
go 學習筆記之解讀什麼是defer延遲函數

  • 當前協程驚慌失措中
go 學習筆記之解讀什麼是defer延遲函數

本文主要介紹了什麼是 defer 延遲函數,通過解讀官方文檔並配套相關代碼認識了延遲函數,但是延遲函數中存在一些可能令人比較迷惑的地方.

go 學習筆記之解讀什麼是defer延遲函數

讀者不妨看一下下面的代碼,將心裡的猜想和實際運行結果比較一下,我們下次再接著分享,感謝你的閱讀.

go 學習筆記之解讀什麼是defer延遲函數

延伸閱讀參考文檔

  • Defer_statements[2]
  • go 語言的 defer 語句[3]
  • Go defer 實現原理剖析[4]
  • go 語言 defer 你不知道的秘密![5]
  • Go 語言中 defer 的一些坑[6]
  • go defer (go 延遲函數)[7]
[1]
Defer statements: https://golang.google.cn/ref/spec#Defer_statements
[2]
Defer_statements: https://golang.google.cn/ref/spec#Defer_statements

[3]
go語言的defer語句: https://www.jianshu.com/p/5b0b36f398a2
[4]
Go defer實現原理剖析: https://studygolang.com/articles/16067
[5]
go語言 defer 你不知道的秘密!: https://www.cnblogs.com/baizx/p/5024547.html
[6]
Go語言中defer的一些坑: https://www.jianshu.com/p/79c029c0bd58
[7]
go defer (go延遲函數): https://www.cnblogs.com/ysherlock/p/8150726.html


分享到:


相關文章: