聊聊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编写一个并发工作队列
May 08 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Golang 获取文件md5校验的方法以及效率对比
May 08 Golang
golang fmt格式“占位符”的实例用法详解
Jul 04 Golang
基于Go语言构建RESTful API服务
Jul 25 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
浅谈GO中的Channel以及死锁的造成
Mar 18 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
Go 中的空白标识符下划线
Mar 25 Golang
golang定时器
Apr 14 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 Golang
GO中sync包自由控制并发示例详解
Aug 05 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
详细介绍:Apache+PHP+MySQL配置攻略
2006/09/05 PHP
PHP COOKIE设置为浏览器进程
2009/06/21 PHP
php中长文章分页显示实现代码
2012/09/29 PHP
PHP以及MYSQL日期比较方法
2012/11/29 PHP
PHP base64编码后解码乱码的解决办法
2014/06/19 PHP
PHP+Ajax实现无刷新分页实例详解(附demo源码下载)
2016/04/07 PHP
判断用户是否在线的代码
2011/03/05 Javascript
jQuery代码优化之基本事件
2011/11/01 Javascript
javascript判断两个IP地址是否在同一个网段的实现思路
2013/12/13 Javascript
js实现屏幕自适应局部代码分享
2015/01/30 Javascript
JS简单生成两个数字之间随机数的方法
2016/08/03 Javascript
AngularJS入门教程之与服务器(Ajax)交互操作示例【附完整demo源码下载】
2016/11/02 Javascript
详解JavaScript 为什么要有 Symbol 类型?
2019/04/03 Javascript
koa2+vue实现登陆及登录状态判断
2019/08/15 Javascript
js实现图片区域可点击大小随意改变(适用移动端)代码实例
2019/09/11 Javascript
js中script的上下放置区别,Dom的增删改创建操作实例分析
2019/12/16 Javascript
JavaScript实现移动小精灵的案例代码
2020/12/12 Javascript
[03:59]DOTA2英雄梦之声_第07期_水晶室女
2014/06/23 DOTA
python通过pil为png图片填充上背景颜色的方法
2015/03/17 Python
Python操作SQLite数据库的方法详解【导入,创建,游标,增删改查等】
2017/07/11 Python
基于Python代码编辑器的选用(详解)
2017/09/13 Python
使用PIL(Python-Imaging)反转图像的颜色方法
2019/01/24 Python
Python完全识别验证码自动登录实例详解
2019/11/24 Python
python爬取本站电子书信息并入库的实现代码
2020/01/20 Python
基于python实现检索标记敏感词并输出
2020/05/07 Python
Chantelle仙黛尔内衣美国官网:法国第一品牌内衣
2018/07/26 全球购物
Skyscanner新西兰:全球领先的旅游搜索网站
2019/08/26 全球购物
大四毕业生学习总结的自我评价
2013/10/31 职场文书
中专毕业生自我鉴定范文
2013/11/09 职场文书
爱之链教学反思
2014/04/30 职场文书
2014最新党员违纪检讨书
2014/10/12 职场文书
2015年元旦晚会活动总结(学生会)
2014/11/28 职场文书
2015年社区消防安全工作总结
2015/10/14 职场文书
MySQL 慢查询日志深入理解
2021/04/22 MySQL
python 命令行传参方法总结
2021/05/25 Python
nginx共享内存的机制详解
2022/03/21 Servers