一.多个延迟执行语句的处理顺序

Go语言中defer语句会将起后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按照defer的逆序进行执行,也就是说先被defer的语句最后执行,最后被defer的语句,最先被执行。

package main
import (
"fmt"
)
func main() {
fmt.Println("defer begin")
// 将defer放入延迟调用栈
defer fmt.Println(1)
defer fmt.Println(2)
// 最后一个放入, 位于栈顶, 最先调用
defer fmt.Println(3)
fmt.Println("defer end")
}

代码输出如下:

defer begin
defer end
3
2
1

结果分析如下:

  • 代码的延迟顺序与最终的执行顺序是反向的。
  • 延迟调用是在 defer 所在函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机时。

二.具体功能

处理业务或逻辑中涉及成对的操作是一件比较烦琐的事情,比如打开和关闭文件、接收请求和回复请求、加锁和解锁等。在这些操作中,最容易忽略的就是在每个函数退出处正确地释放和关闭资源。

defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放问题。

2.1 使用延迟并发解锁

var (
// 一个演示用的映射
valueByKey = make(map[string]int)
// 保证使用映射时的并发安全的互斥锁
valueByKeyGuard sync.Mutex
) // 根据键读取值
func readValue(key string) int {
// 对共享资源加锁
valueByKeyGuard.Lock()
// 取值
v := valueByKey[key]
// 对共享资源解锁
valueByKeyGuard.Unlock()
// 返回值
return v
}

代码说明如下:

  • 第3行,实例化一个map,键是string类型,值为int。
  • 第5行,map默认不是并发安全的,准备一个sync.Mutex互斥量保护map的访问。
  • 第9行,readValue()函数给定一个键,从map中获得值后返回,该函数会在并发环境中使用,需要保证并发安全。
  • 第11行,使用互斥量加锁。
  • 第13行,从map中获取值。
  • 第15行,使用互斥量解锁。
  • 第17行,返回获取到的map值。

    使用 defer 语句对上面的语句进行简化,参考下面的代码。
func readValue(key string) int {
valueByKeyGuard.Lock() // defer后面的语句不会马上调用, 而是延迟到函数结束时调用
defer valueByKeyGuard.Unlock()
return valueByKey[key]
}

上面的代码中第 6~8 行是对前面代码的修改和添加的代码,代码说明如下:

  • 第6行在互斥量加锁后,使用defer语句添加解锁,该语句不会马上执行,而是等readValue()函数返回时才会被执行。
  • 第8行,从map查询值并返回的过程中,与不使用互斥量的写法一样,对比上面的代码,这种写法更简单。

2.2 使用延迟释放文件句柄

文件的操作需要经过打开文件、获取和操作文件资源、关闭资源几个过程,如果在操作完毕后不关闭文件资源,进程将一直无法释放文件资源,在下面的例子中将实现根据文件名获取文件大小的函数,函数中需要打开文件、获取文件大小和关闭文件等操作,由于每一步系统操作都需要进行错误处理,而每一步处理都会造成一次可能的退出,因此就需要在退出时释放资源,而我们需要密切关注在函数退出处正确地释放文件资源,参考下面的代码:

// 根据文件名查询其大小
func fileSize(filename string) int64 {
// 根据文件名打开文件, 返回文件句柄和错误
f, err := os.Open(filename)
// 如果打开时发生错误, 返回文件大小为0
if err != nil {
return 0
}
// 取文件状态信息
info, err := f.Stat() // 如果获取信息时发生错误, 关闭文件并返回文件大小为0
if err != nil {
f.Close()
return 0
}
// 取文件大小
size := info.Size()
// 关闭文件
f.Close() // 返回文件大小
return size
}

代码说明如下:

  • 第 2 行,定义获取文件大小的函数,返回值是 64 位的文件大小值。
  • 第 5 行,使用 os 包提供的函数 Open(),根据给定的文件名打开一个文件,并返回操作文件用的句柄和操作错误。
  • 第 8 行,如果打开的过程中发生错误,如文件没找到、文件被占用等,将返回文件大小为 0。
  • 第 13 行,此时文件句柄 f 可以正常使用,使用 f 的方法 Stat() 来获取文件的信息,获取信息时,可能也会发生错误。
  • 第 16~19 行对错误进行处理,此时文件是正常打开的,为了释放资源,必须要调用 f 的 Close() 方法来关闭文件,否则会发生资源泄露。
  • 第 22 行,获取文件大小。
  • 第 25 行,关闭文件、释放资源。
  • 第 28 行,返回获取到的文件大小。

在上面的例子中,第 25 行是对文件的关闭操作,下面使用 defer 对代码进行简化,代码如下:

func fileSize(filename string) int64 {
f, err := os.Open(filename)
if err != nil {
return 0
}
// 延迟调用Close, 此时Close不会被调用
defer f.Close()
info, err := f.Stat()
if err != nil {
// defer机制触发, 调用Close关闭文件
return 0
}
size := info.Size()
// defer机制触发, 调用Close关闭文件
return size
}

代码中加粗部分为对比前面代码而修改的部分,代码说明如下:

  • 第 10 行,在文件正常打开后,使用 defer,将 f.Close() 延迟调用,注意,不能将这一句代码放在第 4 行空行处,一旦文件打开错误,f 将为空,在延迟语句触发时,将触发宕机错误。
  • 第 16 行和第 22 行,defer 后的语句(f.Close())将会在函数返回前被调用,自动释放资源。

参考链接:http://c.biancheng.net/view/61.html

Golang defer的更多相关文章

  1. golang defer 延后执行什么

    对于golang的defer,我们已经知道,defer定义的语句可以延后到函数返回时执行. 经常用在文件的关闭,锁的释放等场景中.而且defer定义的语句即使遇到panic也会执行.这样,可以执行必要 ...

  2. golang defer的使用

    defer一般用于在函数结束时执行必要的处理工作.例如,关闭文件描述符,关闭网络连接等等. 函数中可以定义多个defer,执行的时候按照先进后出的顺序. defer定义的语句,即使遇到panic,也会 ...

  3. 拒绝滥用golang defer机制

    原文链接 : http://www.bugclosed.com/post/17 defer机制 go语言中的defer提供了在函数返回前执行操作的机制,在需要资源回收的场景非常方便易用(比如文件关闭, ...

  4. golang defer使用——资源关闭时候多用

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

  5. golang defer那些坑

    defer以下几个特性,使用时需要关注下. 即时的参数传递 调用os.Exit()时defer不会被执行 defer与return的先后顺序 1.即时的参数传递 定义defer时传入的参数,是作为拷贝 ...

  6. golang defer 以及 函数栈和return

    defer 作为延迟函数存在,在函数执行结束时才会正式执行,一般用于资源释放等操作 参考一段代码https://mp.weixin.qq.com/s/yfH0CBnUBmH0oxfC2evKBA来分析 ...

  7. golang defer关键字

    defer关键字的作用 defer 会在当前函数或者方法返回(return)之前执行传入的函数.它会经常被用于关闭文件描述符.关闭数据库连接以及解锁资源. 在go语言中,程序有defer语句并不会马上 ...

  8. Golang defer使用

    学习于https://www.liwenzhou.com/posts/Go/function/的文章 1. defer的执行顺序类似于栈,"后进先出",也就是最先defer的语句最 ...

  9. Golang入门教程(十三)延迟函数defer详解

    前言 大家都知道go语言的defer功能很强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦.Go 语言中延迟函数 defer 充当着 try...catch 的重任,使用起来也非常简便,然而在 ...

  10. 探究 Go 语言 defer 语句的三种机制

    Golang 的 1.13 版本 与 1.14 版本对 defer 进行了两次优化,使得 defer 的性能开销在大部分场景下都得到大幅降低,其中到底经历了什么原理? 这是因为这两个版本对 defer ...

随机推荐

  1. mac 源码编译安装php8.3.9

    前提条件 确保你已经安装了 Homebrew 和 Xcode Command Line Tools.你可以通过以下命令安装它们: /bin/zsh -c "$(curl -fsSL http ...

  2. docker push image harbor http 镜像

    前言 搭建的 harbor 仓库为 http 协议,在本地登录后,推送镜像发生如下报错: docker push 192.168.xx.xx/test/grafana:v10.1.1 The push ...

  3. 编写你的第一个 Django 应用程序,第6部分

    本教程从教程 5 停止的地方开始.我们已经构建了一个经过测试的网络投票应用程序,现在我们将添加一个样式表和一个图像. 除了服务器生成的 HTML 之外,Web 应用程序通常需要提供呈现完整网页所需的其 ...

  4. C#实现自己的Json解析器(LALR(1)+miniDFA)

    C#实现自己的Json解析器(LALR(1)+miniDFA) Json是一个用处广泛.文法简单的数据格式.本文介绍如何用bitParser(拥有自己的解析器(C#实现LALR(1)语法解析器和min ...

  5. Tomcat的优化(分别为操作系统优化(内核参数优化),Tomcat配置文件参数优化,Java虚拟机(JVM)调优)

    Tomcat的优化        一.Tomcat 优化                Tomcat 配置文件参数优化        二.系统内核优化        三.Tomcat 配置 JVM 参 ...

  6. StringBuilder案例

    1.案例一 如图 这里无法使用反转方法的原因是,s属于String类型,而反转的方法存在于StringBuilder类型,所以我们要将s的类型转换为StringBuilder String--> ...

  7. 【Linux】3.5 实用指令

    实用指令 1. 指定运行级别(7个级别) 0.关机[一旦开机它就会执行关机] 1.单用户[找回丢失密码] 2.多用户状态没有网络服务 3.多用户状态有网络服务 4.系统未使用保留给用户 5.图形界面 ...

  8. nrm

    nrm npm install -g nrm nrm ls nrm use taobao Tips:不要使用cnpm,会有些奇怪的问题,导致npm install失败. 参考

  9. c#生成一个某文本中不包含的随机字符串

    //生成一个某文本中不包含的随机字符串 private static string GetRandomStr(string allStr) { int number; string resStr; d ...

  10. Unity中检查重复的资源

    目的:检查Unity中资源是否重复,例如有两张贴图,明明是一张,却被复制为两份放在工程中,名字或者所在目录位置不同,这对于资源管理来说是很浪费的.因此需要写一个检查工具来检查项目中是否存在重复的资源. ...