网络编程
位置:首页>> 网络编程>> Go语言>> Go语言defer的一些神奇规则示例详解

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

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

标签:Go语言,defer,规则

测试题

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

0
投稿

猜你喜欢

  • //获取字符数组String.prototype.ToCharArray=function() {    &n
  • 学习目的: 掌握文本框的用法 初次接触try…catch…语法 今天内容很轻松,用一个例子,输入年月日,判断输入是否正确 图片如下: 用个
  • 索引是加速表内容访问的主要手段,特别对涉及多个表的连接的查询更是如此。这是数据库优化中的一个重要内容,我们要了解为什么需要索引,索引如何工作
  • 今天偶尔在一个学习网站技术的地方看到一个教程,关于html代码的,刚看到咱常用到的视频播放器html标签Object,平时用到他的时候都是为
  • IE 浏览器中 CSS Expression 特性的最大的问题:会反复执行,每秒钟可能执行了成百上千次,有严重的性能问题。如何对 CSS E
  • 前言我们在写应用时,基本都会用到配置文件,从各种 shell 到 nginx 等,都有自己的配置文件。虽然这没有太多难度,但是配置项一般相对
  •  本文介绍了10个asp网页制作的常用到的技巧,有asp的也有javascript。如asp如何获取系统时间,如何取得IP,及浏览
  • 在用户体验这个行业,经常会听到,可用性,可访问性这样专业的名词,但是,事实上在很多产品实现过程里都忽略了这一点!WHY?举个很简单的例子,用
  • JavaScript 中的并没有提供像 VBScript 里的 DateAdd 方法用于日
  • 今年年初之时,微软发布了一个针对ActiveX控件的补丁,安装此补丁后的IE6中,当ActiveX控件获得焦点时,IE自动为其套上一个虚线矩
  • 本节讲述单选框/下拉菜单/添加文件,综合css,html和JavaScript实现的,具体详情如下所示:单选框:实现的功能是:(类似平时的性
  • 方法一一般情况下,SQL数据库的收缩并不能很大程度上减小数据库大小,其主要作用是收缩日志大小,应当定期进行此操作以免数据库日志过大1、设置数
  • 一、安装go get github.com/sirupsen/logrus二、使用1、当做标准库使用logrus实现了标准库log的方法,可
  • 有2个不同的方法增加用户:通过使用GRANT语句或通过直接操作MySQL授权表。比较好的方法是使用GRANT语句,因为他们是更简明并且好像错
  • 对象Javascript 根本上是和对象相关的。数组是对象。函数是对象。对象是对象。那什么是对象呢?对象是名-值对的集合。名是字符串,值可以
  • 这篇文章主要介绍了原生Java操作mysql数据库过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要
  • 前言:泛型是静态类型语言的基本特征,允许将类型作为参数传递给另一个类型、函数、或者其他结构。TypeScript 支持泛型作为将类型安全引入
  • 导航栏是一个很好的功能,是Bootstrap 网站的一个突出特点。导航栏是响应式元组件就,作为应用程序或网站的导航标题。导航栏在移动设备的视
  • 在中文网页中最常见的网页编码就是GB2312和UTF-8了,本文介绍了ASP实现GB2312编码转换为UTF-8编码的函数:Function
  •  删除重复记录,将TABLE_NAME中的不重复记录保存到#TABLE_NAME中select distinct&nbs
手机版 网络编程 asp之家 www.aspxhome.com