Golang Defer作用域及执行顺序使用案例
1 defer 作用
在 Golang 中,defer 比面向对象语言中的析构函数要强大很多,defer 还有错误捕获、修改函数返回值、资源释放等,defer 会在当前所在函数返回前执行传入的函数。例如:
func CreateUsers(db *gorm.DB) error { tx := db.Begin() defer tx.Rollback() if err := tx.Create(&Users{Name: "祝融"}).Error; err != nil { return err } return tx.Commit().Error }
注:调用 tx.Commit()
之后执行 tx.Rollback()
并不会影响已经提交事务。
2 defer 作用域
defer 作用域在当前函数和方法返回之前被调用。例如:
package main import "fmt" func main() { { defer fmt.Println("defer done") fmt.Println("code block done") } fmt.Println("main done...") } $ go run main code block done main done defer done
我们会发现,传入的函数不是在退出代码块的作用域时执行的,defer 只会在当前函数和方法返回之前被调用。
3 defer 执行顺序
在 Golang 中,defer 的执行顺序是采用栈(stack)的方式。当你使用多个 defer 语句时,它们会按照后进先出(LIFO)的顺序执行。在一个函数生命周期内,优先调用后面的 defer 。例如:
package main import "fmt" func main() { defer funcA() defer funcB() defer funcC() } func funcA() { fmt.Println("A") } func funcB() { fmt.Println("B") } func funcC() { fmt.Println("C") } $ go run main.go C B A
图解:
3.1 defer、return 谁先执行?
在 Golang 中,return 比 defer 先执行,例如:
package main import "fmt" func deferFunc() int { fmt.Println("defer func done") return 0 } func returnFunc() int { fmt.Println("return func done") return 0 } func returnAndDefer() int { defer deferFunc() return returnFunc() } func main() { returnAndDefer() } $ go run main.go return func done defer func done
3.2 defer 影响主函数的具名返回值
上面讲了 return 比 defer 先执行。
当主函数有返回值,且返回值有没有名字没有关系,defer 所作用的函数,即 defer 可能会影响主函数返回值。看一个例子:
func main() { fmt.Println(deferFuncReturn()) } func deferFuncReturn() (j int) { // t初始化0且作用域为该函数全域 i := 1 defer func() { j++ }() return i } $ go run main.go 2
在举一个例子对比一下:
func main() { fmt.Println(deferFuncReturn()) } func deferFuncReturn() int { i := 1 defer func() { i++ }() return i } $ go run main.go 1
结论:当主函数有返回值 ,会在函数初始化时赋值为0,且其作用域为该函数全域,defer 会影响到该返回值。
3.3 defer 偶遇 panic
在 Golang 中,执行过程中遇到 panic 错误时,遍历所有defer,强行 defer 出栈,并执行 defer。在执行过程中,
- 遇到 recover 捕获异常停止 panic,返回 recover 继续执行
- 未设置 recover 捕获异常,遍历完 defer 抛出 panic 信息
3.3.1 defer 未捕获 panic
package main import ( "fmt" ) func main() { defer_call() fmt.Println("main done...") } func defer_call() { defer func() { fmt.Println("defer func 1") }() defer func() { fmt.Println("defer func 2") }() // 触发defer出 panic("error") defer func() { fmt.Println("defer func 3: no exec") }() } $ go run main.go defer func 2 defer func 1 panic:error ... 堆栈error...
3.3.2 defer 捕获 panic
package main import ( "fmt" ) func main() { defer_call() fmt.Println("main done...") } func defer_call() { defer func() { fmt.Println("defer func 1, 捕获异常") if err := recover(); err != nil { fmt.Println(err) } }() defer func() { fmt.Println("defer func 2, 没有捕获异常") }() // 触发defer出 panic("error") defer func() { fmt.Println("defer func 3: no exec") }() } $ go run main.go defer func 2, 没有捕获异常 defer func 1, 捕获异常 error main done...
总结:从上面可以看出,程序执行中发生了 panic 异常,panic 前的 defer 一定能被执行到,所以我们一般用于关闭资源等,这样一定能保证资源能被关闭,避免一下问题。
3.3.3 defer 中含有 panic
Golang 中,panic 仅会被最后一个 revover 捕获。
package main import ( "fmt" ) func main() { defer func() { if err := recover(); err != nil{ fmt.Println("err:", err) }else { fmt.Println("fatal") } }() defer func() { panic("defer panic2") }() panic("panic1") } $ go run main.go err: defer panic2
在上面例子中,panic("panic1")
先 触发 defer 强制出栈,第一个执行触发 panic("defer panic2)"
异常,此时会覆盖前一个异常 panic
,最后继续执行 defer, 最终被 recover()
捕获住。
3.4 defer 函数嵌套子函数
分析下方代码,有 4 个函数,其中 x 为 1、2、3、4
package main import "fmt" func f(x int, y int) int { fmt.Println("x:", x) return x } func main() { defer f(1, f(3, 0)) defer f(2, f(4, 0)) }
先分析下执行顺序,有 2 个 defer,则会产生 2 次入栈操作,分别是 f1 、f2。
- f1 入栈时,因为形参 y 是一个函数,则需要执行该函数,故执行一次输出 (x:3)
- f2 入栈时,因为形参 y 是一个函数,则需要执行该函数,故执行一次输出(x:4)
main 函数执行完后,执行 defer 函数,所有 defer 出栈,所有执行顺序为 f2、f1。所以程序最终输出的结果是:
$ go run main.go 3 4 2 1
4 思考
一下所有函数传参均为 1。
4.1 defer_fun1
考点:res 的作用域
func defer_fun1(x int) (res int) { res = x defer func() { res += 3 }() return res }
题解:
- 函数有返回值,res 初始化为 0
- res = x 则 res = 1,defer 入栈
- return res 函数结束后,defer 出栈执行 res +3 = 4
最终函数返回结果为 4
4.2 defer_fun2
func defer_fun2(x int) int { res := x defer func() { res += 3 }() return res }
题解:
- res = x 则 res = 1,defer 入栈
- return res 程序结束后,此时函数返回值为1 ,但是返回值不是 res。
- defer 出栈执行 res +3 = 4
最终函数返回结果为 1
4.3 defer_fun3
func defer_fun3(x int) (res int) { defer func() { res += x }() return 2 }
题解:
- 函数有返回值,res 初始化为 0
- x = 1, defer 入栈
- 函数结束res=2,执行 defer 出栈执行 res +1 = 3
最终函数返回结果为 3
4.4 defer_fun4
func defer_fun4() (res int) { t := 1 defer func(x int) { fmt.Println("x:", x) fmt.Println("res:", res) }(t) t = 2 return 4 }
题解:
- 函数有返回值,res 初始化为 0
- t 初始化为1
- defer 入栈,x 作为形参
- 执行t =2,此时主函数返回 4,则 res = 4
- 最后 defer 出栈,控制台输出 x: 1 res: 4
最终函数返回结果为 4。
以上就是Golang Defer作用域及执行顺序使用案例的详细内容,更多关于Golang Defer作用域的资料请关注脚本之家其它相关文章!
相关文章
golang pprof 监控goroutine thread统计原理详解
这篇文章主要为大家介绍了golang pprof 监控goroutine thread统计原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-04-04
最新评论