解密defer語(yǔ)句:避免踩坑,掌握延遲執(zhí)行的正確姿勢(shì)
基本概念
Go語(yǔ)言的延遲語(yǔ)句defer有哪些特點(diǎn)?通常在什么情況下使用?
Go語(yǔ)言的延遲語(yǔ)句(defer statement)具有以下特點(diǎn):
- 延遲執(zhí)行:延遲語(yǔ)句會(huì)在包含它的函數(shù)執(zhí)行結(jié)束前執(zhí)行,無(wú)論函數(shù)是正常返回還是發(fā)生異常。
- 后進(jìn)先出:如果有多個(gè)延遲語(yǔ)句,它們會(huì)按照后進(jìn)先出(LIFO)的順序執(zhí)行。也就是說(shuō),最后一個(gè)延遲語(yǔ)句會(huì)最先執(zhí)行,而第一個(gè)延遲語(yǔ)句會(huì)最后執(zhí)行。
通常情況下,延遲語(yǔ)句在以下情況下使用:
- 資源釋放:延遲語(yǔ)句可以用于在函數(shù)返回前釋放打開(kāi)的文件、關(guān)閉數(shù)據(jù)庫(kù)連接、釋放鎖等資源,以確保資源的正確釋放,避免資源泄漏。
- 錯(cuò)誤處理:延遲語(yǔ)句可以用于處理函數(shù)執(zhí)行過(guò)程中可能發(fā)生的錯(cuò)誤。通過(guò)在函數(shù)開(kāi)始時(shí)設(shè)置延遲語(yǔ)句,在函數(shù)返回前檢查錯(cuò)誤并進(jìn)行相應(yīng)的處理,可以簡(jiǎn)化錯(cuò)誤處理的邏輯。
- 日志記錄:延遲語(yǔ)句可以用于在函數(shù)返回前記錄日志或執(zhí)行其他的調(diào)試操作,以便在函數(shù)執(zhí)行過(guò)程中收集相關(guān)的信息。
延遲語(yǔ)句的使用可以提高代碼的可讀性和可維護(hù)性,同時(shí)確保資源的釋放和清理操作按照逆序進(jìn)行。它是Go語(yǔ)言中一種常用的編程技巧,用于處理資源管理和錯(cuò)誤處理等場(chǎng)景。
避坑之旅
實(shí)際開(kāi)發(fā)中defer的使用并不像前面介紹的這么簡(jiǎn)單,defer用不好,會(huì)陷入泥潭。
下面我從兩個(gè)角度帶大家避坑:
- 首先拆解一下延遲語(yǔ)句的執(zhí)行,注意Go語(yǔ)言的return語(yǔ)句不是原子性的;
- 另外重點(diǎn)和大家分享一下defer語(yǔ)句后面使用匿名函數(shù)和非匿名函數(shù)的區(qū)別。
拆解延遲語(yǔ)句
避免陷入泥潭的關(guān)鍵是必須深刻理解下面這條語(yǔ)句:
return xxx
上面這條語(yǔ)句經(jīng)過(guò)編譯之后,實(shí)際上生成了三條指令:
1)返回值 =xxx。
2)調(diào)用 defer 函數(shù)。
3)空的 return。
第1和第 3 步是return語(yǔ)句生成的指令,也就是說(shuō)return并不是一條原子指令;
第2步是 defer 定義的語(yǔ)句,這里可能會(huì)操作返回值,從而影響最終結(jié)果。
下面來(lái)看兩個(gè)例子,試著將return 語(yǔ)句和 defer語(yǔ)句拆解到正確的順序。
第一個(gè)例子:
func f()(r int){
t:=5
defer func(){
t=t+5
}()
return t
}
拆解后:
func f()(r int){
t:=5
//1,賦值指令
r=t
// 2.defer 被插入到賦值與返回之間執(zhí)行,這個(gè)例子中返回值r沒(méi)被修改過(guò)
func(){
t=t+5
}()
//3.空的 return 指令
return
}
這里第二步實(shí)際上并沒(méi)有操作返回值r,因此,main函數(shù)中調(diào)用f()得到5。
圖片
第二個(gè)例子:
func f()(r int){
defer func(r int){
r=r+5
}(r)
return 1
}
拆解后:
func f() (r int) {
//1.賦值
r=1
//2.這里改的r是之前傳進(jìn)去的r,不會(huì)改變要返回的那個(gè)r值
func(r int) {
r=r+5
}(r)
// 3. 空的 return
return
}
第二步,改變的是傳值進(jìn)去的r,是形參的一個(gè)復(fù)制值,不會(huì)影響實(shí)參r。因此,main函數(shù)中需要調(diào)用f()得到1。
圖片
在使用匿名函數(shù)和非匿名函數(shù)作為defer的參數(shù)時(shí),主要區(qū)別在于對(duì)函數(shù)參數(shù)的傳遞和作用域的影響:
- 匿名函數(shù)作為defer的參數(shù):匿名函數(shù)可以直接在defer語(yǔ)句中定義,可以訪問(wèn)外部函數(shù)的變量,并且在執(zhí)行時(shí)會(huì)使用當(dāng)前的變量值。這種方式可以方便地在defer語(yǔ)句中使用外部變量,但需要注意變量的值在執(zhí)行時(shí)可能已經(jīng)發(fā)生了改變。
- 非匿名函數(shù)作為defer的參數(shù):非匿名函數(shù)需要先定義好,然后作為defer的參數(shù)傳遞。在執(zhí)行時(shí),會(huì)使用函數(shù)的當(dāng)前參數(shù)值。這種方式可以在defer語(yǔ)句中使用已定義的函數(shù),但需要注意函數(shù)參數(shù)的傳遞和作用域。
產(chǎn)生這種區(qū)別的原因是,匿名函數(shù)和非匿名函數(shù)在定義和作用域上的差異。匿名函數(shù)可以直接在defer語(yǔ)句中定義,可以訪問(wèn)外部函數(shù)的變量,而非匿名函數(shù)需要先定義好,然后作為參數(shù)傳遞。這種設(shè)計(jì)靈活性使得開(kāi)發(fā)者可以根據(jù)具體的需求選擇合適的方式來(lái)使用defer語(yǔ)句。
舉例來(lái)說(shuō)
當(dāng)使用匿名函數(shù)作為defer的參數(shù)時(shí),可以在defer語(yǔ)句中直接定義匿名函數(shù),并訪問(wèn)外部變量。
以下是一個(gè)示例代碼:
package main
import "fmt"
func main() {
x := 10
defer func() {
fmt.Println("Deferred anonymous function:", x)
}()
x = 20
fmt.Println("Before return:", x)
}
在上述示例中,匿名函數(shù)作為defer的參數(shù),可以訪問(wèn)外部變量x。在函數(shù)返回之前,defer語(yǔ)句中的匿名函數(shù)會(huì)執(zhí)行,并打印出x的值。
輸出結(jié)果如下:
圖片
當(dāng)使用非匿名函數(shù)作為defer的參數(shù)時(shí),需要先定義好函數(shù),然后將函數(shù)名作為defer的參數(shù)傳遞。
以下是一個(gè)示例代碼:
package main
import "fmt"
func main() {
x := 10
defer printX(x)
x = 20
fmt.Println("Before return:", x)
}
func printX(x int) {
fmt.Println("Deferred function:", x)
}
在上述示例中,printX函數(shù)作為defer的參數(shù)傳遞,函數(shù)定義在main函數(shù)之后。
在函數(shù)返回之前,defer語(yǔ)句中的printX函數(shù)會(huì)執(zhí)行,并打印出傳遞的參數(shù)x的值。輸出結(jié)果如下:
圖片
總結(jié)一下
通過(guò)以上示例,我們可以明確體現(xiàn)出使用匿名函數(shù)和非匿名函數(shù)作為defer的參數(shù)的區(qū)別。
匿名函數(shù)可以直接在defer語(yǔ)句中定義,并訪問(wèn)外部變量,而非匿名函數(shù)需要先定義好函數(shù),然后將函數(shù)名作為參數(shù)傳遞。
通過(guò)前面帶著大家拆解了defer的語(yǔ)句的執(zhí)行,相信大家可以更好的理解了。
本文轉(zhuǎn)載自微信公眾號(hào)「 程序員升級(jí)打怪之旅」,作者「 王中陽(yáng)Go」,可以通過(guò)以下二維碼關(guān)注。
轉(zhuǎn)載本文請(qǐng)聯(lián)系「 程序員升級(jí)打怪之旅」公眾號(hào)。