Go語(yǔ)言defer的一些神奇規(guī)則示例詳解
測(cè)試題
defer有一些規(guī)則,如果不了解,代碼實(shí)現(xiàn)的最終結(jié)果會(huì)與預(yù)期不一致。對(duì)于這些規(guī)則,你了解嗎?
這是關(guān)于defer使用的代碼,可以先考慮一下返回值。
package main
import (
"fmt"
)
/**
* @Author: Jason Pang
* @Description: 快照
*/
func deferFuncParameter1() {
var aInt = 1
defer fmt.Println(aInt)
aInt = 2
return
}
/**
* @Author: Jason Pang
* @Description: 快照
*/
func deferFuncParameter2() {
var aInt = 1
defer func(t int) {
fmt.Println(t)
}(aInt)
aInt = 2
return
}
/**
* @Author: Jason Pang
* @Description: 動(dòng)態(tài)
*/
func deferFuncParameter3() {
var aInt = 1
defer func() {
fmt.Println(aInt)
}()
aInt = 2
return
}
/**
* @Author: Jason Pang
* @Description: 影響返回值
* @return ret
*/
func deferFuncReturn1() (ret int) {
ret = 10
defer func() {
ret++
fmt.Println("-----", ret)
}()
return 2
}
/**
* @Author: Jason Pang
* @Description: 不影響返回值
* @return ret
*/
func deferFuncReturn2() (ret int) {
ret = 10
defer func(ret int) {
ret++
fmt.Println("-----", ret)
}(ret)
return 2
}
/**
* @Author: Jason Pang
* @Description: defer順序
*/
func deferFuncSeq1() {
var aInt = 1
defer fmt.Println(aInt)
aInt = 2
defer fmt.Println(aInt)
return
}
func main() {
fmt.Println("快照")
deferFuncParameter1()
deferFuncParameter2()
deferFuncParameter3()
fmt.Println("返回值")
fmt.Println(deferFuncReturn1())
fmt.Println(deferFuncReturn2())
fmt.Println("執(zhí)行順序")
deferFuncSeq1()
}
正確輸出為:
? myproject go run main.go
快照
1
1
2
返回值
----- 3
3
----- 11
2
執(zhí)行順序
2
1
分析
defer有幾條重要規(guī)則,上面的結(jié)果都能從這些規(guī)則中找到答案。
規(guī)則一當(dāng)defer被聲明時(shí),其參數(shù)就會(huì)被實(shí)時(shí)解析
當(dāng)defer被聲明的時(shí)候,如果直接使用了參數(shù),此時(shí)的參數(shù)就會(huì)使用快照值,在整個(gè)生命周期內(nèi)不會(huì)變化。如deferFuncParameter1、deferFuncParameter2,雖然aInt在defer聲明后被變更,但defer里的值不會(huì)再變了。
func deferFuncParameter1() {
var aInt = 1
defer fmt.Println(aInt)
aInt = 2
return
}
func deferFuncParameter2() {
var aInt = 1
defer func(t int) {
fmt.Println(t)
}(aInt)
aInt = 2
return
}
與之相反的是deferFuncParameter3,隨aInt的變化而變化。
func deferFuncParameter3() {
var aInt = 1
defer func() {
fmt.Println(aInt)
}()
aInt = 2
return
}
規(guī)則二 defer可能操作主函數(shù)的具名返回值
defer有可能更改函數(shù)的返回值,這是最容易導(dǎo)致錯(cuò)誤的地方。
關(guān)鍵字_return_不是一個(gè)原子操作,實(shí)際上_return_只代理匯編指令_ret_,即將跳轉(zhuǎn)程序執(zhí)行。比如語(yǔ)句 return i ,實(shí)際上分兩步進(jìn)行,即將i值存入棧中作為返回值,然后執(zhí)行跳轉(zhuǎn),而defer的執(zhí)行時(shí)機(jī)正是跳轉(zhuǎn)前,所以說(shuō)defer執(zhí)行時(shí)還是有機(jī)會(huì)操作返回值的。return i的執(zhí)行過(guò)程如下所示:
result = i
執(zhí)行defer
return
所以基于這個(gè)規(guī)則,對(duì)于deferFuncReturn1,
func deferFuncReturn1() (ret int) {
ret = 10
defer func() {
ret++
fmt.Println("-----", ret)
}()
return 2
}
執(zhí)行過(guò)程為:
ret = 2
ret++
fmt.Println("-----", ret)
return
所以最終ret的值為3。
對(duì)于deferFuncReturn2,因?yàn)閐efer聲明的時(shí)候直接使用了參數(shù),所以使用的是快照,不會(huì)影響ret的返回值。
規(guī)則三 延遲函數(shù)執(zhí)行按后進(jìn)先出順序執(zhí)行
即先出現(xiàn)的 defer最后執(zhí)行
這個(gè)規(guī)則大家都很熟悉,defer按照棧的順序執(zhí)行。
坑實(shí)例
舉一個(gè)錯(cuò)誤使用defer的實(shí)例。在go中使用事務(wù)時(shí),有一種推薦寫(xiě)法:將Rollback放到defer中,通過(guò)判斷函數(shù)是否有報(bào)錯(cuò)或者panic,來(lái)判斷是否要回滾。
func Update() (resp *baseinfo.Resp, err error) {
//開(kāi)啟事務(wù)
panicked := true
tx, err := db.TXBegin()
if err != nil {
return resp, nil
}
defer func() {
if panicked || err != nil {
tx.Rollback()
}
}()
//更新
err = h.update(shopId, tx)
if err != nil {//失敗返回
return resp, nil
}
panicked = false
err = tx.Commit().Error
if err != nil { //失敗返回
return resp, nil
}
return
}
判斷回滾的err正是函數(shù)的具名返回值,在有報(bào)錯(cuò)的情況下,返回值被賦值為nil,這意味如果有失敗,Rollback也不會(huì)被執(zhí)行。
之所以不將err直接返回,而是使用nil,是因?yàn)榭蚣茉O(shè)計(jì)的問(wèn)題,業(yè)務(wù)錯(cuò)誤通過(guò)resp返回,如果直接返回err,框架會(huì)認(rèn)為是RPC錯(cuò)誤。
以上就是Go語(yǔ)言defer的一些神奇規(guī)則示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言defer規(guī)則的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言安裝和GoLand2021最全超詳細(xì)安裝教程
探索分析Go?HTTP?GET請(qǐng)求發(fā)送body

