聊聊golang中多个defer的执行顺序


Posted in Golang onMay 08, 2021

golang 中多个 defer 的执行顺序

引用 Ture Go 中的一个示例:

package main
import "fmt"
func main() {
    fmt.Println("counting")
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("done")
}

程序执行结果为:

counting

done

9

8

7

6

5

4

3

2

1

0

从结果可以看出,defer 的执行可以看做是一个 FILO(First In Last Out) 栈。

在编写程序时,如果遇到下面的执行流程,id1 先获取资源,然后 id2 通过 id1 获取,而 id2 的释放 Close 必须要在 id1 之前,如下:

func fun() {
    id1 := Open()
    ...
    id2 := id1.Open()
    ...
    id2.Close()
    id1.Close()
}

如果使用defer,其执行顺序和上面完全相同的,所以我们通常在 Open 打开资源后,立即使用 defer Close,不会引起释放顺序问题。

func fun() {
    id1 := Open()
    defer id1.Close()
    ...
    id2 := id1.Open()
    id2.Close()
    ...
}

defer 压入栈的是值,如果为函数,则可以修改变量值

func c() (i int) {
    defer func() { i++ }()
    return 1
}

如上代码,压入栈的是一个函数地址,函数执行完后,执行i++,会改变返回值,函数返回值为 2。

从上面的通过defer修改返回值,defer也可以用于控制恢复panic断言。

package main
import "fmt"
func main() {
    f()
    fmt.Println("Returned normally from f.")
}
func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}
func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

执行结果为:

Calling g.

Printing in g 0

Printing in g 1

Printing in g 2

Printing in g 3

Panicking!

Defer in g 3

Defer in g 2

Defer in g 1

Defer in g 0

Recovered in f 4

Returned normally from f.

补充:Golang中defer的三个实战要点

前言

Golang中的defer是使用频次比较高的,能创造出延迟生效特效的一种方式。

defer也有自己的矫情,需要注意的。

本文将从通过代码的方式来说明defer的三点矫情。

1.defer的生效顺序

2.defer与return,函数返回值之间的顺序

3.defer定义和执行两个步骤,做的事情。

正文

1.defer的生效顺序

先说结论:defer的执行顺序是倒序执行(同入栈先进后出)

func main() {
 defer func() {
  fmt.Println("我后出来")
 }()
 defer func() {
  fmt.Println("我先出来")
 }()
}

执行后打印出:

我先出来

我后出来

2.defer与return,函数返回值之间的顺序

先说结论:return最先执行->return负责将结果写入返回值中->接着defer开始执行一些收尾工作->最后函数携带当前返回值退出

返回值的表达方式,我们知道根据是否提前声明有两种方式:一种是func test() int 另一种是 func test() (i int),所以两种情况都来说说

func test() int
func main() {
 fmt.Println("main:", test())
}
func test() int {
 var i int
 defer func() {
  i++
  fmt.Println("defer2的值:", i)
 }()
 defer func() {
  i++
  fmt.Println("defer1的值:", i)
 }()
 return i
}

输出:

defer1的值: 1

defer2的值: 2

main: 0

详解:return的时候已经先将返回值给定义下来了,就是0,由于i是在函数内部声明所以即使在defer中进行了++操作,也不会影响return的时候做的决定。

func test() (i int)
func main() {
 fmt.Println("main:", test())
}
func test() (i int) {
 defer func() {
  i++
  fmt.Println("defer2的值:", i)
 }()
 defer func() {
  i++
  fmt.Println("defer1的值:", i)
 }()
 return i
}

输出:

defer1的值: 1

defer2的值: 2

main: 2

详解:由于返回值提前声明了,所以在return的时候决定的返回值还是0,但是后面两个defer执行后进行了两次++,将i的值变为2,待defer执行完后,函数将i值进行了返回。

3.defer定义和执行两个步骤,做的事情

先说结论:会先将defer后函数的参数部分的值(或者地址)给先下来【你可以理解为()里头的会先确定】,后面函数执行完,才会执行defer后函数的{}中的逻辑

func test(i *int) int {
 return *i
}
func main(){
 var i = 1
 // defer定义的时候test(&i)的值就已经定了,是1,后面就不会变了
 defer fmt.Println("i1 ="  , test(&i))
 i++
 // defer定义的时候test(&i)的值就已经定了,是2,后面就不会变了
 defer fmt.Println("i2 ="  , test(&i))
 // defer定义的时候,i就已经确定了是一个指针类型,地址上的值变了,这里跟着变
 defer func(i *int) {
  fmt.Println("i3 ="  , *i)
 }(&i)
 // defer定义的时候i的值就已经定了,是2,后面就不会变了
 defer func(i int) {
  //defer 在定义的时候就定了
  fmt.Println("i4 ="  , i)
 }(i)
 defer func() {
  // 地址,所以后续跟着变
  var c = &i
  fmt.Println("i5 ="  , *c)
 }()
 
 // 执行了 i=11 后才调用,此时i值已是11
 defer func() {
  fmt.Println("i6 ="  , i)
 }()
 i = 11
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
golang判断key是否在map中的代码
Apr 24 Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 Golang
解决Go gorm踩过的坑
Apr 30 Golang
golang协程池模拟实现群发邮件功能
May 02 Golang
Golang 编译成DLL文件的操作
May 06 Golang
Go 自定义package包设置与导入操作
May 06 Golang
go语言基础 seek光标位置os包的使用
May 09 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Go gorilla securecookie库的安装使用详解
Aug 14 Golang
Golang全局变量加锁的问题解决
golang 实现并发求和
May 08 #Golang
golang中的并发和并行
May 08 #Golang
关于golang高并发的实现与注意事项说明
May 08 #Golang
基于Golang 高并发问题的解决方案
May 08 #Golang
使用golang编写一个并发工作队列
May 08 #Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 #Golang
You might like
咖啡语言
2021/03/03 咖啡文化
PHP session常见问题集锦及解决办法总结
2007/03/18 PHP
浅析echo(),print(),print_r(),return之间的区别
2013/11/27 PHP
destoon调用自定义模板及样式的公告栏
2014/06/21 PHP
php redis实现对200w用户的即时推送
2017/03/04 PHP
PHP实现的各类hash算法长度及性能测试实例
2017/08/27 PHP
Laravel中批量赋值Mass-Assignment的真正含义详解
2017/09/29 PHP
YII框架行为behaviors用法示例
2019/04/26 PHP
JS input 数字验证代码
2009/07/30 Javascript
Js放到HTML文件中的哪个位置有什么区别
2013/08/21 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
2014/02/21 Javascript
javascript使用正则获取url上的某个参数
2014/09/04 Javascript
JS实现状态栏跑马灯文字效果代码
2015/10/24 Javascript
js 点击a标签 获取a的自定义属性方法
2016/11/21 Javascript
Express之get,pos请求参数的获取
2017/05/02 Javascript
React组件之间的通信的实例代码
2017/06/27 Javascript
Vue学习笔记进阶篇之多元素及多组件过渡
2017/07/19 Javascript
vue二级路由设置方法
2018/02/09 Javascript
发布一款npm包帮助理解npm的使用
2019/01/03 Javascript
js前端如何写一个精确的倒计时代码
2019/10/25 Javascript
Python简单实现子网掩码转换的方法
2016/04/13 Python
Python网络爬虫实例讲解
2016/04/28 Python
Python实现多进程共享数据的方法分析
2017/12/04 Python
python3安装speech语音模块的方法
2018/12/24 Python
西班牙英格列斯百货法国官网:El Corte Inglés法国
2017/07/09 全球购物
教师实习期自我鉴定
2013/10/06 职场文书
绝对经典成功的大学生推荐信
2013/11/08 职场文书
先进党支部事迹材料
2014/01/13 职场文书
学生请假条格式
2014/04/11 职场文书
治超工作实施方案
2014/05/04 职场文书
办公室务虚会发言材料
2014/10/20 职场文书
2014年保卫部工作总结
2014/11/21 职场文书
英文慰问信
2015/02/14 职场文书
2015年老干部工作总结
2015/04/23 职场文书
2015年“世界无车日”活动方案
2015/05/06 职场文书
SpringBoot深入分析讲解监听器模式下
2022/07/15 Java/Android