Golang 语言中的 defer 怎么使用?

简介: Golang 语言中的 defer 怎么使用?

介绍

在 Golang 语言中,我们可以在函数(自定义和部分内置)或方法中使用 defer 关键字注册延迟调用(一个或多个),多个延迟调用的执行顺序是先进后出(FILO)。并且不会受到函数执行结束退出,显式调用 return 和主动(或被动)触发 panic 的影响,注册成功的所有延迟调用都会被执行,除非 defer 注册在 return 之后或者函数(或方法)调用 os.Exit(1)

defer 注册多个延迟调用,执行顺序是先进后出(FILO)。

示例代码:

func main () {
 defer func() {
  fmt.Println("A")
 }()
 defer func() {
  fmt.Println("B")
 }()
 fmt.Println("main goroutine run over")
 // panic("this is a panic example")
 // return
}

defer 如果定义在 return 之后,它等于 defer 没有注册,将不会执行。

示例代码:

func main () {
 fmt.Println("main")
 return
 defer func() {
  fmt.Println("A")
 }()
}

defer 所在的函数或方法中,如果调用 os.Exit(1),defer 即便注册,也不会执行。

示例代码:

func main () {
 defer func() {
  fmt.Println("A")
 }()
 fmt.Println("main")
 os.Exit(1)
}

defer 必须在函数和方法中才可以使用,并且 defer 后面必须是函数(自定义和部分内置函数)或方法,defer 函数的实参是值拷贝。

示例代码

func main () {
 a := 0
 defer func(num int) {
  fmt.Println("defer func()", num)
 }(a)
 a++
 fmt.Println(a)
}

02

使用场景

使用关键字 defer 注册的函数(自定义和部分内置)或方法,因为不会受到函数执行结束,显式调用 return 和主动或被动)触发 panic 的影响,通常会用于防止忘记释放资源和捕获 panic(同一 goroutine 中) 防止应用程序崩溃退出的应用场景。

示例代码:

func main () {
 f, err := os.OpenFile("text.txt", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0755)
 if err != nil {
  fmt.Println(err)
 }
 defer f.Close()
 n, err := f.WriteString("this is a text file\t")
 if err != nil {
  fmt.Println(err)
 }
 fmt.Println(n)
}

阅读上面这段代码,我们使用 defer 延迟调用释放资源,防止忘记释放资源(关闭文件或解锁),通常 defer 会放在错误检查之后。

示例代码:

func main () {
 defer func() {
  if err := recover(); err != nil {
   fmt.Println("this is a panic" )
  }
 }()
 panic("this is a test panic")
 fmt.Println("main")
}

阅读上面这段代码,我们使用 defer 配合 recover 函数,用于拦截 panic(同一 goroutine 中),防止程序崩溃退出。

03

注意事项

虽然使用 defer 具有可以用于防止忘记释放资源和拦截 panic(同一 goroutine 中)防止应用程序崩溃退出等好处。

但是 defer 也有副作用,它会使资源延迟释放,defer 尽量不要再 for-loop 中使用,并且相比于未使用 defer 调用的函数(自定义和部分内置)或方法,defer 也有一定的性能损耗,Golang 语言官方也在 golang 1.13 和 golang 1.14 中优化了 defer 的性能。

相比于 defer 的性能损耗,defer 带来的使代码更加优雅、可读和健壮等优势,我认为 defer 综合来看,利大于弊,它可以给 gopher 们带来的收益比付出的代价更大。所以,我建议大家尽量使用 defer。

还有一点需要注意的是,我们不要使用 defer 调用有返回值的自定义函数或方法,返回值会丢失,可能会给应用程序带来意想不到的错误。

04

总结

本文我们介绍了 defer 的执行机制,使用场景和注意事项,并且给出了相应的示例代码。通常我们会在 Golang 语言开发中使用 defer 防止忘记释放资源(关闭文件或解锁)和捕获 panic(同一 goroutine 中) 防止应用程序崩溃退出。

关于 defer 的原理分析,限于篇幅,并没有花费笔墨,感兴趣的读者朋友们,建议自行搜索相关资料了解相关内容。

推荐阅读:

Golang 语言的编程技巧之类型

Golang 语言的编程技巧之变量

Golang 语言中的非类型安全指针

参考资料:

https://gobyexample-cn.github.io/defer 

https://blog.golang.org/defer-panic-and-recover 


目录
相关文章
|
7月前
|
存储 编译器 Go
Golang 语言的多种变量声明方式和使用场景
Golang 语言的多种变量声明方式和使用场景
33 0
|
7月前
|
缓存 编译器 Go
Golang 语言 vendor 在 GOPATH 和 Modules 中的区别
Golang 语言 vendor 在 GOPATH 和 Modules 中的区别
31 0
|
2月前
|
SQL 前端开发 Go
编程笔记 GOLANG基础 001 为什么要学习Go语言
编程笔记 GOLANG基础 001 为什么要学习Go语言
|
7月前
|
存储 JSON Go
Golang 语言 gRPC 服务怎么同时支持 gRPC 和 HTTP 客户端调用?
Golang 语言 gRPC 服务怎么同时支持 gRPC 和 HTTP 客户端调用?
76 0
|
4月前
|
物联网 Go 网络性能优化
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式
使用Go语言(Golang)可以实现MQTT协议的点对点(P2P)消息发送。MQTT协议本身支持多种消息收发模式【1月更文挑战第21天】【1月更文挑战第104篇】
109 1
|
4天前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
14 1
|
5天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello("Alice")`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
13 1
|
5天前
|
SQL 关系型数据库 MySQL
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
Golang数据库编程详解 | 深入浅出Go语言原生数据库编程
|
6天前
|
Go 开发者
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
【4月更文挑战第21天】本文介绍了Go语言中的流程控制语句,包括`if`、`switch`和`for`循环。`if`语句支持简洁的语法和初始化语句,但需注意比较运算符的使用。`switch`语句提供多分支匹配,可省略`break`,同时支持不带表达式的形式。`for`循环有多种形式,如基本循环和`for-range`遍历,遍历时修改原集合可能导致未定义行为。理解并避免易错点能提高代码质量和稳定性。通过实践代码示例,可以更好地掌握Go语言的流程控制。
11 3
Golang深入浅出之-Go语言流程控制:if、switch、for循环详解
|
6天前
|
Go
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值
【4月更文挑战第21天】Go语言函数是代码组织的基本单元,用于封装可重用逻辑。本文介绍了函数定义(包括基本形式、命名、参数列表和多返回值)、调用以及匿名函数与闭包。在函数定义时,注意参数命名和注释,避免参数顺序混淆。在调用时,要检查并处理多返回值中的错误。理解闭包原理,小心处理外部变量引用,以提升代码质量和可维护性。通过实践和示例,能更好地掌握Go语言函数。
19 1
Golang深入浅出之-Go语言函数基础:定义、调用与多返回值