Go语言defer的一些神奇规则示例详解

作者:程序员麻辣烫 时间:2023-10-18 05:03:01 

测试题

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

标签:Go语言,defer,规则
0
投稿

猜你喜欢

  • Oracle学习笔记(五)

    2024-01-25 04:12:09
  • 网站508规范(译)

    2008-04-03 13:26:00
  • asp经典入门教程 在ASP中使用SQL 语句

    2013-06-01 20:23:21
  • 详解pandas获取Dataframe元素值的几种方法

    2022-12-28 07:30:01
  • Windows 下更改 jupyterlab 默认启动位置的教程详解

    2023-06-11 13:10:12
  • django框架中ajax的使用及避开CSRF 验证的方式详解

    2023-05-11 02:10:41
  • JS实现在线ps功能详解

    2024-05-13 09:19:02
  • Python使用pptx实现复制页面到其他PPT中

    2021-07-13 01:28:16
  • element 结合vue 在表单验证时有值却提示错误的解决办法

    2023-07-02 16:57:12
  • 详解Python中的条件判断语句

    2022-05-03 09:33:28
  • golang 实现两个结构体复制字段

    2024-05-22 10:21:50
  • jQuery Easyui实现左右布局

    2024-04-09 19:48:22
  • Python脚本实现Web漏洞扫描工具

    2023-05-01 04:18:50
  • Go实现分布式系统高可用限流器实战

    2024-02-18 14:40:10
  • 通俗易懂详解Python基础五种下划线作用

    2024-01-01 06:36:22
  • Python进制转换用法详解

    2021-08-20 15:18:40
  • IE 8 提出“超级标准模式”

    2008-01-24 19:26:00
  • JS代码混淆加密工具

    2008-05-25 13:49:00
  • golang中使用匿名结构体的方法

    2023-07-10 07:26:56
  • vue封装axios与api接口管理的完整步骤

    2024-04-30 10:28:24
  • asp之家 网络编程 m.aspxhome.com