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

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
}

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

(0)

相关推荐

  • Golang之defer 延迟调用操作

    前言 defer语句被用于预定对一个函数的调用.我们把这类被defer语句调用的函数称为延迟函数.而defer 延迟语句在其他编程语言里好像没有见到.应该是属于 Go 语言里的独有的关键字.但用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块. 下面对defer进行介绍. defer特性 1. 关键字 defer 用于注册延迟调用. 2. 这些调用直到 return 前才被执.因此,可以用来做资源清理. 3. 多个defer语句,按先进后出的方式执行. 1.延迟调用 用法

  • Golang 的defer执行规则说明

    defer介绍 defer是golang的一个特色功能,被称为"延迟调用函数".当外部函数返回后执行defer.类似于其他语言的 try- catch - finally- 中的finally,当然差别还是明显的. 在使用defer之前我们应该多了解defer的特性,这样才能避免使用上的误区. 1. 最简单的defer func test(){ defer func(){ fmt.Println("defer") }() //todo //... return //

  • 聊聊golang的defer的使用

    序 本文主要研究一下golang的defer defer return先赋值(对于命名返回值),然后执行defer,最后函数返回 defer函数调用的执行顺序与它们分别所属的defer语句的执行顺序相反 defer后面的表达式可以是func或者是method的调用,如果defer的函数为nil,则会panic 实例 实例1 // f returns 42 func f() (result int) { defer func() { // result is accessed after it w

  • golang中defer的关键特性示例详解

    前言 大家都知道golang的defer关键字,它可以在函数返回前执行一些操作,最常用的就是打开一个资源(例如一个文件.数据库连接等)时就用defer延迟关闭改资源,以免引起内存泄漏.本文主要给大家介绍了关于golang中defer的关键特性,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 一.defer 的作用和执行时机 go 的 defer 语句是用来延迟执行函数的,而且延迟发生在调用函数 return 之后,比如 func a() int { defer b() return

  • golang中defer的使用规则详解

    前言 在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内部变量. 为了更好的学习defer的行为,我们首先来看下面一段代码: func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil {

  • Golang学习笔记之延迟函数(defer)的使用小结

    golang的defer优雅又简洁, 是golang的亮点之一.defer在声明时不会立即执行,而是在函数return后,再按照先进后出的原则依次执行每个defer,一般用于释放资源.清理数据.记录日志.异常处理等. 关键字defer于注册延迟调用.这些调用直到 ret 前才被执行,通常用于释放资源或错误处理. 一.当defer被声明时,其参数就会被实时解析 func a() { i := 0 defer fmt.Println(i) //输出0,因为i此时就是0 i++ defer fmt.P

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

    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 从结

  • 对for循环中表达式和循环体的执行顺序详解

    对于学c的朋友来说,for循环可能使我们经常用到的一种循环语句 for(表达式1:表达式2:表达式3){循环体} 知道其的语句执行顺序对我们来说可以避免很多失误 我们可以利用下面这个小程序轻易测出其内在的语句循环顺序: #include<stdio.h> void main() { int i; for (printf("#1\n"),i=1; printf("#2\n"),i<=5; printf("#3\n"),i++) {

  • 简单了解java中静态初始化块的执行顺序

    这篇文章主要介绍了简单了解java中静态初始化块的执行顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在java中,其应该是先于所有的方法执行. 下面是测试代码: public class Test1 { static{ System.out.println("执行静态初始化块test1..."); } { System.out.println("执行初始化块test1"); } public Test1(){

  • vue-router中的钩子函数和执行顺序说明

    目录 一:全局导航钩子函数 1.vue router.beforeEach(全局前置守卫) 2.vue router.afterEach(全局后置守卫) 二:路由独享的守卫(路由内钩子) 三:组件内的守卫(组件内钩子) 1.beforeRouteEnter.beforeRouteUpdate.beforeRouteLeave 2.路由钩子在实际开发中的应用场景 vue-router执行顺序 一:全局导航钩子函数 1.vue router.beforeEach(全局前置守卫) beforeEach

  • 简单聊聊Golang中defer预计算参数

    目录 什么是defer Go语言defer预计算参数 总结 什么是defer defer用来声明一个延迟函数,把这个函数放入到一个栈上, 当外部的包含方法return之前,返回参数到调用方法之前调用,也可以说是运行到最外层方法体的"}"时调用.我们经常用他来做一些资源的释放,比如关闭io操作 func doSomething(fileName string) { file,err := os.Open(fileName) if err != nil { panic(err) } def

  • 关于SQL语句中的AND和OR执行顺序遇到的问题

    问题 昨天在写数据库SQL的时候遇到一个问题.问题的根结在SQL语句的AND和OR关键字的执行优先级问题.下面就针对这个问题进行一下测试. 场景 1.有一张学生表Student,表字段包括Id(用户主键).Name(用户名).Grade(年级).Class(班级).Sex(性别).如下: 表结构 2.在表中导入十条测试数据,如下: 表数据 3.现需要查询出性别为女的1年级女学生,或者性别为女的2班级女学生.SQL语句如下:     select * from student where sex=

  • 数据库中的SELECT语句逻辑执行顺序分析

    引言 这不是一个什么多深的技术问题,多么牛叉的编程能力.这跟一个人的开发能力也没有非常必然的直接关系,但是知道这些会对你的SQL编写,排忧及优化上会有很大的帮助.它不是一个复杂的知识点,但是一个非常基础的SQL根基.不了解这些,你一直用普通水泥盖房子:掌握这些,你是在用高等水泥盖房子. 然而,就是这么一个小小的知识点,大家可以去调查一下周围的同事朋友,没准你会得到一个"惊喜". 由于这篇文章是突然有感而写,下面随手编写的SQL语句没有经过测试. 看下面的几段SQL语句: 复制代码 代码

  • 聊聊Golang中很好用的viper配置模块

    前言 viper 支持Yaml.Json. TOML.HCL 等格式,读取非常的方便. 安装 go get github.com/spf13/viper 如果提示找不到golang.org/x/text/这个库,是因为golang.org/x/text/这个库在GitHub上托管的路径不一致. 解决办法: 可以从https://github.com/golang/text下载源码下来,然后到$GOPATH/src下面创建golang.org/x/文件夹(已存在的忽略),把压缩包的文件解压到gol

  • SQL Server 查询处理中的各个阶段(SQL执行顺序)示例

    在大数编程语言中,代码按编码顺序被处理,但是在SQL语言中,第一个被处理的子句是FROM子句,尽管SELECT语句第一个出现,但是几乎总是最后被处理. 每个步骤都会产生一个虚拟表,该虚拟表被用作下一个步骤的输入.这些虚拟表对调用者(客户端应用程序或者外部查询)不可用.只是最后一步生成的表才会返回 给调用者.如果没有在查询中指定某一子句,将跳过相应的步骤.下面是对应用于SQL server 2000和SQL Server 2005的各个逻辑步骤的简单描述. 复制代码 代码如下: (8)SELECT

随机推荐