Go 語言中有個 defer 關鍵字,常用於實現延遲函數
延遲函數就是這麼一種機制,無論程序是正常返回還是異常報錯,只要存在延遲函數都能保證這部分關鍵邏輯最終執行,所以用來做些資源清理等操作再合適不過了.
出入成雙有始有終
日常開發編程中,有些操作總是成雙成對出現的,有開始就有結束,有打開就要關閉,還有一些連續依賴關係等等.
一般來說,我們需要控制結束語句,在合適的位置和時機控制結束語句,手動保證整個程序有始有終,不遺漏清理收尾操作.
最常見的拷貝文件操作大致流程如下:
打開源文件io.Copy(dstFile, srcFile)
關閉目標文件
dstFile.Close()
srcFile.Close()
關閉源文件
srcFile.Close()
值得注意的是: 這種拷貝文件的操作需要特別注意操作順序而且也不要忘記釋放資源,比如先打開再關閉等等!
「雪之夢技術驛站」: 上述代碼邏輯還是清晰簡單的,可能不會忘記釋放資源也能保證操作順序,但是如果邏輯代碼比較複雜的情況,這時候就有一定的實現難度了!
可能是為了簡化類似代碼的邏輯,Go 語言引入了 defer 關鍵字,創造了"延遲函數"的概念.
無 defer 的文件拷貝上述示例代碼簡單展示了 defer 關鍵字的基本使用方式,顯著的好處在於 Open/Close 是一對操作,不會因為寫到最後而忘記 Close 操作,而且連續依賴時也能正常保證延遲時機.
簡而言之,如果函數內部存在連續依賴關係,也就是說創建順序是 A->B->C 而銷燬順序是 C->B->A.這時候使用 defer 關鍵字最合適不過.
懶人福音延遲函數
官方文檔相關表述見 Defer statements[1]
如果沒有 defer 延遲函數前,普通函數正常運行:
當添加 defer 關鍵字實現延遲後,原來的 1 被推遲到 2 後面而不是之前的 1 2 順序.
如果存在多個 defer 關鍵字,執行順序可想而知,越往後的越先執行,這樣才能保證按照依賴順序依次釋放資源.
相信你已經明白了多個 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 前依次逆序執行.
周圍函數到達函數體
reached the
周圍函數的函數體運行到結尾前逆序執行多個 defer 語句,即先輸出 3 後依次輸出 2 1. 最終函數的輸出結果是 3 2 1 ,也就說是沒有 return 聲明也能保證結束前執行完 defer 延遲函數.
當前協程正驚慌失措
because the corresponding goroutine is panicking
周圍函數萬一發生 panic 時也會先運行前面已經定義好的 defer 語句,而 panic 後續代碼因為沒有特殊處理,所以程序崩潰了也就無法運行.
函數的最終輸出結果是 3 2 1 panic ,如此看來 defer 延遲函數還是非常盡忠職守的,雖然心裡很慌但還是能保證老弱病殘先行撤退!
通過解讀 defer 延遲函數的定義以及相關示例,相信已經講清楚什麼是 defer 延遲函數了吧?
簡單地說,延遲函數就是一種
所以不論是正常運行還是異常運行,提前做好預案總是沒錯的,基本上可以保證萬無一失,所以不妨考慮考慮 defer 延遲函數?
go-error-about-lovely.png
延遲函數應用場景
基本上成雙成對的操作都可以使用延遲函數,尤其是申請的資源前後存在依賴關係時更應該使用 defer 關鍵字來簡化處理邏輯.
下面舉兩個常見例子來說明延遲函數的應用場景.
Open/Close文件操作一般會涉及到打開和開閉操作,尤其是文件之間拷貝操作更是有著嚴格的順序,只需要按照申請資源的順序緊跟著defer 就可以滿足資源釋放操作.
鎖的申請和釋放是保證同步的一種重要機制,需要申請多個鎖資源時可能存在依賴關係,不妨嘗試一下延遲函數!
總結以及下節預告
defer 延遲函數是保障關鍵邏輯正常運行的一種機制,如果存在多個延遲函數的話,一般會按照逆序
延遲函數的運行時機一般有三種情況:
周圍函數遇到返回時本文主要介紹了什麼是 defer 延遲函數,通過解讀官方文檔並配套相關代碼認識了延遲函數,但是延遲函數中存在一些可能令人比較迷惑的地方.
讀者不妨看一下下面的代碼,將心裡的猜想和實際運行結果比較一下,我們下次再接著分享,感謝你的閱讀.
延伸閱讀參考文檔
Defer_statements[2]go 語言的 defer 語句[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