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


猜你喜欢
- 利用numpy、matplotlib、sympy绘制sigmoid、tanh、ReLU、leaky ReLU、softMax函数起因:深度学
- 1.官网下载:https://dev.mysql.com/downloads/找到Mysql Community Server 点击点击do
- ThinkPHP提供的视图查询应用功能十分强大,用户利用视图查询功能可以将多个数据表的字段内容按需要进行指定和筛选,组织成一个基于这些数据表
- 这次我主要讲解如何用Python基于Flask的登录和注册,验证方式采用Basic Auth 主要用以下库import os#Flask的基
- 首先,必须安装vuex的依赖npm install vuex --save-dev创建专属vuex的文件夹和store.js:store.j
- 301和302 Http状态有啥区别?301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于:301 redir
- MySQL存储过程SAVEPOINT ROLLBACK to示例如下:DELIMITER $$DROP PROCEDURE IF EXIST
- 实验目的主要是获取2021年今日说法每期节目主要内容及时间今日说法的网址为:http://tv.cctv.com/lm/jrsf/index
- 背景 background css 说明 background-image:url(&q
- SymPy是符号数学的Python库。它的目标是成为一个全功能的计算机代数系统,同时保持代码简洁、易于理解和扩展#coding:utf-8&
- Vue合并el-table第一列相同数据业务需求需要将el-table表格第一列相同的内容进行合并。解决办法el-table中使用 :spa
- 写在前面我的 CUDA 版本是什么? 这个问题本身就是有问题的,因为没有搞清楚cuda的分类这里的 CUDA 说的是 Driver CUDA
- 一、游戏玩法介绍:24点游戏是儿时玩的主要益智类游戏之一,玩法为:从一副扑克中抽取4张牌,对4张牌使用加减乘除中的任何方法,使计算结果为24
- 如何使用GPU而不是CPU首先查看设备from tensorflow.python.client import device_libprin
- 很多设计师都会遇到这样的问题。一个产品会有很多种方式去包装,其中包括很多功能和很多体验。功能越多会被认为越实用,体验越好会被认为越方便。方便
- 读取csv文件时添加表头/列名有时,我们读取的csv文件数据时发现没有表头/列名,是因为Python读取csv文件数据本来就没有表头,用pa
- 前面已经介绍过几种基本语句(print,import,赋值语句),下面我们来介绍条件语句,循环语句。一. print和import的更多信息
- Oracle数据库以其高可靠性、安全性、可兼容性,得到越来越多的企业的青睐。如何使Oracle数据库保持优良性能,这是许多数据库管理员关心的
- Python 多进程和数据传递的理解python不仅线程用的是系统原生线程,进程也是用的原生进程进程的用法和线程大同小异import mul
- 不止python,你可以利用任何语言那实现通过http请求来操作你自己的小程序云数据库了背景也是在最近吧,小程序更新了云开发 HTTP AP