golang的defer的理解- defer的函数一定会执行吗?
文章目录
- golang的defer
- 什么是defer
- 理解defer
- defer什么时间执行(defer、 return、返回值 三者的执行顺序)
- defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(注意引用情况)
- 多个defer,执行顺序
- defer的函数一定会执行么?
- panic情况
- os.Exit情况
- kill情况(Ctrl+C)
- 参考
golang的defer
什么是defer
defer的的官方文档:https://golang.org/ref/spec#Defer_statements
go语言中defer可以完成延迟功能,当前函数执行完成后再执行defer的代码块。通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。
defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。
Go语言机制担保一定会执行defer语句中的代码。其它语言中也有类似的机制,比如Java、C#语言里的finally语句,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。
理解defer
defer什么时间执行(defer、 return、返回值 三者的执行顺序)
defer只有在当前函数执行完毕后,才会执行。描述其实不太精确
go中的return语句并不是原子性操作,一般是分为两步:
- 将返回值赋值给一个变量
- 执行RET指令
return并不是原子性操作,是通过一个变量赋值和ret指令来完成的。defer就执行在1之后,2之前。defer的执行顺序在return之后,但是在返回值返回给调用方之前,所以使用defer可以达到修改返回值的目的。
defer、 return、返回值 三者的执行顺序是 : return 最先给返回值赋值;接着 defer 开始执行一些收尾工作;最后 RET 指令携带返回值退出函数。
package mainimport ("fmt"
)func main() {ret := test()fmt.Println("test return:", ret)
}
// func test() ( int) { 这种就是匿名返回值
//返回值改为命名返回值, 具名返回值。即返回值带有名字, 这样我们在执行defer的时候相当于修改了返回值的值
func test() (i int) {//var i intdefer func() {i++fmt.Println("test defer, i = ", i)}()return i
}
注意: 这块验证使用了具名返回值
func test() (i int) {
中的(i int)
。 测试结果满足我们预期。
编码中,我们要特别注意, go语言中匿名返回值和命名返回值对defer的影响。不过一般我们都是使用命名返回值。
一个主函数拥有一个匿名的返回值,返回时使用字面值,比如返回”1”、”2”、”Hello”这样的值,这种情况下defer语句是无法操作返回值的。
defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(注意引用情况)
defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?
package mainimport "fmt"func test1() {i := 0defer fmt.Println(i)i++return
}func main() {test1()
}
输出结果:0
虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。
总结: 因为defer后面的函数在入栈的时候保存的是入栈那一刻的值,而当时i的值是0,所以后期对i修改,并不会影响栈内函数的值。
我们再看下一个例子:
package mainimport "fmt"func test2() {x := 10defer func(a *int) {fmt.Println(*a)}(&x)x++
}func main() {test2()
}
输出结果: 11
这里为什么和前面结论不一样呢?
这里defer后面函数入栈的时候存入的执行变量x的指针。所以,后期x值改变的时候,输出结果也会改变。
总结: 需要注意引用情况。对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer后面的语句对变量的修改会影响延迟函数。
多个defer,执行顺序
package mainimport ("fmt"
)func main() {defer fmt.Println("main defer1")test()defer fmt.Println("main defer2")
}func test() () {defer func() {fmt.Println("test defer1")}()defer func() {fmt.Println("test defer2")}()
}
输出结果:
test defer2
test defer1
main defer2
main defer1
总结: 后进先出(LIFO)的顺序执行,即先出现的 defer 最后执行。即:多个defer语句的执行顺序是逆序执行。
defer的函数一定会执行么?
defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。
Go语言机制担保一定会执行defer语句中的代码。其它语言中也有类似的机制,比如Java、C#语言里的finally语句,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。
panic情况
网上demo:
package mainimport ("fmt"
)func test1() {fmt.Println("test")
}func test2() {panic(1)
}
func main() {fmt.Println("main start")defer test1()test2() //造panicfmt.Println("main end")
}
执行结果:
main start
test
panic: 1 goroutine 1 [running]:
main.test2(...)
总结: 我们发现正常的panic,还是会调我们的defer的,并且在会在panic之前执行。
os.Exit情况
网上demo:
package mainimport ("fmt""os"
)func test1() {fmt.Println("test")
}func main() {fmt.Println("main start")defer test1()fmt.Println("main end")os.Exit(0)
}
执行结果:
main start
main end
总结: 如果在当前函数里是因为执行了os.Exit退出,而不是正常return退出或者panic退出,那程序会立即停止,被defer的函数调用不会执行。
kill情况(Ctrl+C)
package mainimport ("fmt""time"
)func test1() {fmt.Println("test")
}func test2() {time.Sleep(60 * time.Second)
}
func main() {fmt.Println("main start")defer test1()test2()fmt.Println("main end")
}
执行结果:
main startProcess finished with the exit code -1073741510 (0xC000013A: interrupted by Ctrl+C)
如上,我们test2()
睡眠时间内,点击Ctrl+C
,发现defer test1()
并没有执行。
总结:这个点很重要,需要我们在日常异常中断时,留意defer是否未处理的情况。
所以一般情况下,我们程序需要捕获这种异常中断,在程序退出前,手动做一些处理。
参考
Go常见坑:Go语言里被defer的函数一定会执行么?
参考URL: https://blog.csdn.net/perfumekristy/article/details/121343642
面试官:听说你精通golang的defer?
参考URL: https://cloud.tencent.com/developer/article/2076951
golang的defer的理解- defer的函数一定会执行吗?相关推荐
- Golang:简介、基本语法、函数、defer、Test功能
春招找实习告一段落了,好长时间没更CSDN的博客,期间写的一些笔记用 typora + git 直接推到github里面了,就没在CSDN里再发了,我的github:https://github.co ...
- Go 学习笔记(17)— 函数(03)[defer 定义、defer 特点、defer 释放资源]
1. defer 定义 Go 函数的关键字 defer 可以提供注册多个延迟调用,只能出现在函数内部,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,这些调用遵循 ...
- golang源码分析:defer流程分析
defer defer是golang中使用的延迟调用的函数,该函数的使用场景就是如果函数执行出错(panic),也能够通过recover方式进行捕捉错误并将出错时的一些资源进行回收,如果在性能有要求的 ...
- golang善用go func和defer
一.error与panic: error:可预见的错误 panic:不可预见的错误,panic一般通过defer中的recover()捕获 对于有风险的代码,若发生panic则会导致程序异常退出,例如 ...
- Go进阶(1): Golang + Goland 研究Redis的基本操作与函数接口
1. 开发环境搭建 GOROOT变量值是安装的go路径 PATH环境变量就是%GOROOT%\bin路径 GOPATH环境变量是工作目录,就是写代码的目录,编译源代码等生成的文件都会放到这个目录下 N ...
- Golang中闭包的理解
简介 参考博客: https://www.calhoun.io/what-is-a-closure/ https://blog.cloudflare.com/a-go-gotcha-when-clos ...
- golang导入包的理解
golang导入包的理解 1.首先是包的引入原理 程序的初始化和执行都起始于main包.如果main包还导入了其它的包,那么就会在编译时将它们依次导入.有时一个包会被多个包同时导入,那么它只会被 ...
- python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递
python 的 深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是&q ...
- 深入浅出理解c++虚函数
深入浅出理解c++虚函数 记得几个月前看过C++虚函数的问题,当时其实就看懂了,最近笔试中遇到了虚函数竟然不太确定,所以还是理解的不深刻,所以想通过这篇文章来巩固下. 装逼一刻: 最近,本人思想发生了 ...
最新文章
- 通过仿真和综合认识D触发器(Verilog HDL语言描述D触发器)
- MySQL查询日志总结
- 本地如何预览php文件上传,如何实现js上传图片本地预览同时支持预览截图的功能...
- mysql导出txt到client_mysql导出导入txt以及sftp自动下载(一)
- php mysql int string_mysql查出的 int 型字段都是 string
- 从 ThinkPHP 开发规范 看 PHP 的命名规范和开发建议
- 苹果Iphone/Ipad--L2T虚拟教程
- 前端学习(3294):effect hook
- 11 怎么给字符串加索引
- soapUI(groovy脚本作用2)请不要问为什么系列2
- day09 python之函数进阶
- 距离之和最小 V3 51Nod - 1110(带权中位数或者爆搜)
- 关于Session、Cookie、Token你知道多少?
- 专网视频会议直播系统整合部署方案附拓扑图
- 数据分析之第三方支付业务
- 阿里云部署flask项目
- Linux下Docker安装微信文件传输问题
- java毕业设计SEOUL设计师品牌代购商城Mybatis+系统+数据库+调试部署
- Windows containers are not supported by your Windows version. Check documentation for minimum requir
- Java中产生随机数的两个方法