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

 更新时间:2022年11月17日 16:15:42   作者:程序员麻辣烫  
这篇文章主要为大家介绍了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错误。

以上就是Go语言defer的一些神奇规则示例详解的详细内容,更多关于Go语言defer规则的资料请关注脚本之家其它相关文章!

相关文章

  • Golang并发编程重点讲解

    Golang并发编程重点讲解

    这篇文章主要介绍了Golang并发编程,在许多环境中,实现对共享变量的正确访问所需要的微妙之处使并发编程变得困难。Go鼓励一种不同的方法,在这种方法中,共享值在通道中传递,实际上,从不由单独的执行线程主动共享
    2023-04-04
  • golang并发编程的实现

    golang并发编程的实现

    这篇文章主要介绍了golang并发编程的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • 深入了解Go语言中sync.Pool的使用

    深入了解Go语言中sync.Pool的使用

    本文将介绍 Go 语言中的 sync.Pool并发原语,包括sync.Pool的基本使用方法、使用注意事项等的内容,对我们了解Go语言有一定的帮助,需要的可以参考一下
    2023-04-04
  • Go语言将string解析为time.Time时两种常见报错

    Go语言将string解析为time.Time时两种常见报错

    本文主要介绍了Go语言将string解析为time.Time时两种常见报错,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • GO env环境变量配置命令使用

    GO env环境变量配置命令使用

    这篇文章主要为大家介绍了GO env环境变量配置命令使用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Go语言实现lru淘汰策略和超时过期

    Go语言实现lru淘汰策略和超时过期

    缓存的大小是有限的,当添加数据发现剩余缓存不够时,需要淘汰缓存中的部分数据,本文主要介绍了Go语言实现lru淘汰策略和超时过期,感兴趣的可以了解一下
    2024-02-02
  • Go语言编程中对文件读写的基本方法整理

    Go语言编程中对文件读写的基本方法整理

    这篇文章主要介绍了Go语言编程中对文件读写的基本方法整理,是Go语言入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • go kratos源码及配置解析

    go kratos源码及配置解析

    这篇文章主要为大家介绍了go kratos源码及配置解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • Go标准库http server优雅启动深入理解

    Go标准库http server优雅启动深入理解

    这篇文章主要介绍了Go标准库http server优雅启动深入理解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Golang实现KV存储引擎实例探究

    Golang实现KV存储引擎实例探究

    这篇文章主要为大家介绍了Golang实现KV存储引擎实例探究,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01

最新评论