你是不是覺得defer很簡單、很好用,但也許你掉坑裏了都不知道!
這篇文章不介紹defer的常用功能,而是介紹你在用defer時,也許會踩的坑。
defer允許我們進行一些函數執行完成後的收尾工作,並且代碼更加簡潔,例如:
-
關閉文件流:
// open a file defer file.Close()
-
解鎖一個加鎖的資源
mu.Lock() defer mu.Unlock()
-
打印最終報告
printHeader() defer printFooter()
-
關閉數據庫鏈接
// open a database connection defer disconnectFromDB()
但是:
- 你知道defer和defer後的函數什麼時候執行嗎?
- 你知道defer後函數裏的變量值是什麼時候計算的嗎?
我曾經在Stack Overflow討論過這個問題,有興趣的可以看下,本打算在週末寫個文章分享給大家defer的坑,今天不小心瀏覽到一個有誤解的文章,決定現在就寫下來,希望大家不要踩坑。
defer陷阱測試
如果下面這段代碼的結果你都知道,恭喜你,你已經瞭解defer的執行原理,沒有必要再看這篇文章了。
func test1() (x int) {
defer fmt.Printf("in defer: x = %d\n", x)
x = 7
return 9
}
func test2() (x int) {
x = 7
defer fmt.Printf("in defer: x = %d\n", x)
return 9
}
func test3() (x int) {
defer func() {
fmt.Printf("in defer: x = %d\n", x)
}()
x = 7
return 9
}
func test4() (x int) {
defer func(n int) {
fmt.Printf("in defer x as parameter: x = %d\n", n)
fmt.Printf("in defer x after return: x = %d\n", x)
}(x)
x = 7
return 9
}
func main() {
fmt.Println("test1")
fmt.Printf("in main: x = %d\n", test1())
fmt.Println("test2")
fmt.Printf("in main: x = %d\n", test2())
fmt.Println("test3")
fmt.Printf("in main: x = %d\n", test3())
fmt.Println("test4")
fmt.Printf("in main: x = %d\n", test4())
}
你已經計算出結果了嗎?看看和運行結果是不是一樣的,如果不一樣繼續閱讀本文吧:
test1
in defer: x = 0
in main: x = 9
test2
in defer: x = 7
in main: x = 9
test3
in defer: x = 9
in main: x = 9
test4
in defer x as parameter: x = 0
in defer x after return: x = 9
in main: x = 9
defer執行原理
要想知道爲何是這個結果,就得先回答前面的2個問題:
- defer和defer後的函數什麼時候執行嗎?
- defer後函數裏的變量值是什麼時候計算的嗎?
依次來回答,這2個問題。
問題1:defer在defer語句處執行,defer的執行結果是把defer後的函數壓入到棧,等待return或者函數panic後,再按先進後出的順序執行被defer的函數。
問題2:defer的函數的參數是在執行defer時計算的,defer的函數中的變量的值是在函數執行時計算的。
defer及defer函數的執行順序分2步:
- 執行defer,計算函數的入參的值,並傳遞給函數,但不執行函數,而是將函數壓入棧。
- 函數return語句後,或panic後,執行壓入棧的函數,函數中變量的值,此時會被計算。
defer測試解析
這4個測試函數中,都是return 9
並且沒有對返回值進行修改,所以main中都是in main: x = 9
,我相信這個大家應該是沒有疑問的。接下來看每個測試函數defer的打印。
test1:defer執行時,對Printf
的入參x進行計算,它的值是0,並且傳遞給函數,return 9
後執行Printf
,所以結果是in defer: x = 0
。
test2:與test1類似,不同僅是,defer執行是在x=7
之後,所以x的值是7,並且傳遞給Printf
,所以結果是:in defer: x = 7
。
test3:defer後跟的是一個匿名函數,匿名函數能訪問外部函數的變量,這裏訪問的是test3的x,defer執行時,匿名函數沒有入參,所以把func()()
壓入到棧,return語句之後,執行func()()
,此時匿名函數獲得x的值是9,所以結果是in defer: x = 9
。
test4:與test3的不同是,匿名函數有一個入參n,我們把x作爲入參打印,還有就是匿名函數訪問外部打印x。defer執行時,x=0
,所以入棧的函數是func(int)(0)
,return語句之後執行func(int)(0)
,即n=0
,x在匿名函數內沒有定義,依然訪問test4中的x,此時x=9
,所以結果爲:in defer x as parameter: x = 0, in defer x after return: x = 9
。
誤解文章截圖
最後,看下誤解讀者文章的截圖,看看你能不能發現那篇文章作者的思路問題。
上文的作者的目的想知道defer是在return之前,還是之後執行,所以做了這麼個測試,他把上面的代碼和修改成下面的代碼,發現等效後,就給出了錯誤結論:defer確實是在return之前調用的。
等效的能證明,順序嗎?請各位自行思考吧。
defer的核心
Golang對於defer的介紹)很精簡,但是把上面提到的問題都說清楚了,我也是讀了幾遍和其他人交流,才完全理解透,不妨好好讀讀,最核心的一句:
Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.
參考資料
如果這篇文章對你有幫助,請點個贊/喜歡,讓我知道我的寫作是有價值的,感謝。