Go語言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í)行。比如語句 return i ,實(shí)際上分兩步進(jìn)行,即將i值存入棧中作為返回值,然后執(zhí)行跳轉(zhuǎn),而defer的執(zhí)行時(shí)機(jī)正是跳轉(zhuǎn)前,所以說defer執(zhí)行時(shí)還是有機(jī)會(huì)操作返回值的。return i的執(zhí)行過程如下所示:
result = i
執(zhí)行defer
return
所以基于這個(gè)規(guī)則,對(duì)于deferFuncReturn1,
func deferFuncReturn1() (ret int) { ret = 10 defer func() { ret++ fmt.Println("-----", ret) }() return 2 }
執(zhí)行過程為:
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í),有一種推薦寫法:將Rollback放到defer中,通過判斷函數(shù)是否有報(bào)錯(cuò)或者panic,來判斷是否要回滾。
func Update() (resp *baseinfo.Resp, err error) { //開啟事務(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ì)的問題,業(yè)務(wù)錯(cuò)誤通過resp返回,如果直接返回err,框架會(huì)認(rèn)為是RPC錯(cuò)誤。
以上就是Go語言defer的一些神奇規(guī)則示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go語言defer規(guī)則的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言配置數(shù)據(jù)庫連接池的實(shí)現(xiàn)
本文內(nèi)容我們將解釋連接池背后是如何工作的,并探索如何配置數(shù)據(jù)庫能改變或優(yōu)化其性能。文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12Golang?IOT中的數(shù)據(jù)序列化與解析過程
這篇文章主要介紹了Golang?IOT中的數(shù)據(jù)序列化與解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05Go實(shí)現(xiàn)短url項(xiàng)目的方法示例
這篇文章主要介紹了Go實(shí)現(xiàn)短url項(xiàng)目的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03