日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Go語言defer的一些神奇規則示例詳解_Golang

作者:程序員麻辣燙 ? 更新時間: 2022-12-14 編程語言

測試題

defer有一些規則,如果不了解,代碼實現的最終結果會與預期不一致。對于這些規則,你了解嗎?

這是關于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: 動態
 */
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("執行順序")
	deferFuncSeq1()
}

正確輸出為:

? myproject go run main.go

快照

1

1

2

返回值

----- 3

3

----- 11

2

執行順序

2

1

分析

defer有幾條重要規則,上面的結果都能從這些規則中找到答案。

規則一當defer被聲明時,其參數就會被實時解析

當defer被聲明的時候,如果直接使用了參數,此時的參數就會使用快照值,在整個生命周期內不會變化。如deferFuncParameter1、deferFuncParameter2,雖然aInt在defer聲明后被變更,但defer里的值不會再變了。

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
}

規則二 defer可能操作主函數的具名返回值

defer有可能更改函數的返回值,這是最容易導致錯誤的地方。

關鍵字_return_不是一個原子操作,實際上_return_只代理匯編指令_ret_,即將跳轉程序執行。比如語句 return i ,實際上分兩步進行,即將i值存入棧中作為返回值,然后執行跳轉,而defer的執行時機正是跳轉前,所以說defer執行時還是有機會操作返回值的。return i的執行過程如下所示:

result = i?
執行defer
return

所以基于這個規則,對于deferFuncReturn1,

func deferFuncReturn1() (ret int) {
	ret = 10
	defer func() {
		ret++
		fmt.Println("-----", ret)
	}()
	return 2
}

執行過程為:

ret = 2
ret++
fmt.Println("-----", ret)
return

所以最終ret的值為3。

對于deferFuncReturn2,因為defer聲明的時候直接使用了參數,所以使用的是快照,不會影響ret的返回值。

規則三 延遲函數執行按后進先出順序執行

即先出現的 defer最后執行

這個規則大家都很熟悉,defer按照棧的順序執行。

坑實例

舉一個錯誤使用defer的實例。在go中使用事務時,有一種推薦寫法:將Rollback放到defer中,通過判斷函數是否有報錯或者panic,來判斷是否要回滾。

func  Update() (resp *baseinfo.Resp, err error) {
	//開啟事務
	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正是函數的具名返回值,在有報錯的情況下,返回值被賦值為nil,這意味如果有失敗,Rollback也不會被執行。

之所以不將err直接返回,而是使用nil,是因為框架設計的問題,業務錯誤通過resp返回,如果直接返回err,框架會認為是RPC錯誤。

原文鏈接:https://juejin.cn/post/7068854815589138439

欄目分類
最近更新