引言

Go 语言中的 defer 语句是 UNIX 之父 Ken Thompson 大神发明的,是完全正交的设计。

也正因为 Go 语言遵循的是正交的设计, 所以才有了: “少是指数级的多/Less is exponentially more” 的说法。因为是正交的设计,最终得到的组合形式是指数级的组合形式。

作用

在 go 语言中,defer 代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是 return 之后添加一个函数调用。因此,defer 通常用来释放函数内部变量。

通过 defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。defer 作为 go 语言清理变量的特性,有其独有且明确的行为。

使用规则

当 defer 被声明时,其参数就会被实时解析

func a() {i := 0defer fmt.Println(i)i++return
}

结果输出的是0。

通过运行结果,可以看到 defer 输出的值,就是定义时的值。而不是 defer 真正执行时的变量值。

defer 执行顺序为先进后出

当同时定义了多个 defer 代码块时,go 安装先定义后执行的顺序依次调用 defer。我们用下面的代码加深记忆和理解:

func b() {for i := 0; i < 4; i++ {defer fmt.Print(i)}
}

在循环中,依次定义了四个 defe r代码块。结合规则一,我们可以明确得知每个defer代码块应该输出什么值。 安装先进后出的原则,我们可以看到依次输出了3210。

defer 可以读取有名返回值

看如下代码:

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

输出结果是12。defer 是在 return 调用之后才执行的。 这里需要明确的是 defer 代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer 的作用域仍然在 c 函数之内。因此 defer仍然可以读取 c 函数内的变量。

当执行return 1 之后,i的值就是1。 此时此刻,defer 代码块开始执行,对 i 进行自增操作。 因此输出 2。

使用场景

简化资源的回收

这是最常见的 defer 用法。 比如:

mu.Lock()
defer mu.Unlock()

当然, defer 也有一定的开销, 也有为了节省性能而回避使用的 defer 的:

mu.Lock()
count++
mu.Unlock()

从简化资源的释放角度看, defer 类似一个语法糖, 好像不是必须的。

panic 异常的捕获

defer 除了用于简化资源的释放外, 还是 Go 语言异常框架的一个组成部分。 Go 语言中, panic 用于抛出异常, recover 用于捕获异常。recover只能在defer语句中使用, 直接调用recover是无效的。

比如:

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()fmt.Println("Returned normally from g.")
}func g() {panic("ERROR")
}

修改返回值

defer 除了用于配合 recover,用于捕获 panic 异常外,defer还可以用于在 return 之后修改函数的返回值。

func doubleSum(a, b int) (sum int) {defer func() {   //该函数在函数返回时  调用sum *= 2}()sum = a + b
}

安全的回收资源

前面第一点提到, defer 最常见的用法是简化资源的回收。 而且,从资源回收角度看,defer 只是一个语法糖。

其实,,也不完全是这样,,特别是在涉及到第二点提到的 panic 异常等因素导致 goroutine 提前退出时。

比如,,有一个线程安全的 slice 修改函数,为了性能没有使用 defer 语句:

func set(mu *sync.Mutex, arr []int, i, v int) {mu.Lock()arr[i] = vmu.Unlock()
}

但是, 如果 i >= len(arr) 的话,,runtime 就会抛出切片越界的异常(这里只是举例, 实际开发中不应该出现切片越界异常)。 这样的话,mu.Unlock() 就没有机会被执行了。

如果用 defer 的话,即使出现异常也能保证 mu.Unlock() 被调用:

func set(mu *sync.Mutex, arr []int, i, v int) {mu.Lock()defer mu.Unlock()arr[i] = v
}

当然,Go 语言约定异常不会跨越 package 边界。 因此, 调用一般函数的时候不用担心 goroutine 异常退出的情况。

实例

一个例子:

func CopyFile(dst, src string) (w int64, err error) {srcFile, err := os.Open(src)if err != nil {return}defer srcFile.Close() //每次申请资源时,请习惯立即申请一个 defer 关闭资源,这样就不会忘记释放资源了dstFile, err := os.Create(dst)if err != nil {return}defer dstFile.Close()return io.Copy(dstFile, srcFile)
}

defer 还有一个重要的特性,就是即便函数抛出了异常,也会被执行的。 这样就不会因程序出现了错误,而导致资源不会释放了。

参考文章

https://studygolang.com/articles/10167
https://studygolang.com/articles/5932

Go语言 defer相关推荐

  1. Go语言defer详解笔记

    Go语言defer详解 1.defer概述: ​ defer是用来声明一个延迟函数,并且将这个函数放到一个栈中,它的调用时间在return执行之前,详细来讲,它的执行时间在return的值赋值之后,在 ...

  2. Go语言defer关键字

    Go语言的defer关键字用于延迟调用,下面是关于Go语言defer关键字的一些基础概念: 1. defer关键字用于注册延迟调用: 2. 这些调用直到包含当前该defer关键字的函数执行完了才会被执 ...

  3. Go语言defer详解

    1. 使用defer的优势 defer一般用于资源的释放和异常的捕捉, 作为Go语言的特性之一. defer 语句会将其后面跟随的语句进行延迟处理. 意思就是说 跟在defer后面的语言 将会在程序进 ...

  4. go语言defer的作用

    1.第一种情况,.differ后面只能跟着函数调用逻辑,且是压栈操作,先入后出 如下代码: package mainimport "fmt"func main() {//go语言中 ...

  5. java怎么延迟执行语句_Go语言defer(延迟执行语句)

    Go语言中关键字defer允许我们推迟到函数返回之前(或任意位置执行return语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为return语句同样可以包含一些操作,而不是单 ...

  6. go语言defer使用

    defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句.当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回.特别是当你在进行一些打开资源 ...

  7. Go 语言 defer recover panic 简单例子

    为什么80%的码农都做不了架构师?>>>    // Mydef project main.go package mainimport ("log" )func ...

  8. 万字长文剖析清楚 Go 语言 defer 原理

    大纲 编译器怎么编译 defer `struct _defer` 数据结构 `struct _defer` 内存分配 执行 defer 函数链( `deferreturn`  ) defer 怎么传递 ...

  9. go语言的defer语句

    go语言defer语句的用法 参考:https://www.jianshu.com/p/5b0b36f398a2 defer的语法 defer后面必须是函数调用语句,不能是其他语句,否则编译器会出错. ...

最新文章

  1. Linux查看本机外网ip
  2. Java IO: File
  3. 使用Maven配置JBoss / Wildfly数据源
  4. 第七节:实战前必须掌握的10个指令(上)
  5. android java11,Android RxJava1 入门教程
  6. SpringBoot:HttpMessageNotWritableException: No converter found for return value of type
  7. 什么是 DevSecOps?系列(一)
  8. cartographer探秘第四章之代码解析(八) --- 生成地图
  9. 关于String内存分配的深入探讨
  10. 计算机控制技术复试面试(一)
  11. ​ZMC运动控制器SCARA机械手应用快速入门
  12. rtl8211 smi读取_RTL8211E应用(二)之信号输入、输出接口
  13. 国潮风格设计,具象化插画作品|打开你的头脑风暴
  14. 一个链表L 一个链表P 包含升序排列的整数 操作PrintLots(L,P)将打印L中那些由P所指定的位置上的元素
  15. 纯净简洁绿色的解压缩软件
  16. 软考高级 真题 2013年上半年 信息系统项目管理师 综合知识
  17. 虎年降至.一款2022虎年为主的一款头像制作小程序源码。
  18. 华为3D建模服务(3D Modeling Kit),轻松构建高质量3D模型
  19. redux与flux
  20. Unable to locate tools.jar. Expected to find it in C:/Program Files/Java/jre

热门文章

  1. 华为开源只用加法的神经网络:实习生领衔打造,效果不输传统CNN | CVPR 2020 Oral...
  2. AI如何反低俗?今日头条推内容检测工具“灵犬”3.0,首次公开其技术原理
  3. 个人在 laravel 开发中使用到的一些技巧(持续更新)
  4. ASP.NET 2.0的编译模型
  5. new Function()
  6. RamDisk加速Windows 7?
  7. 华硕笑傲珠峰,网络口碑营销巧打奥运擦边球
  8. Cloudify — Overview
  9. 5G NR — 动态频谱共享
  10. 微服务架构 — Overview