golang——defer
defer是什么
defer是Go语言提供的一种用于注册延迟调用的机制,让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。
defer与panic和recover结合,形成了go语言风格的异常与捕获机制
常用于一些成对操作的场景:
打开连接/关闭连接;加锁/释放锁;打开文件/关闭文件等。
package mainimport "os"func main() {f, err := os.Open(filename)if err != nil {panic(err)}if f != nil {defer f.Close()} }
优点:方便使用
缺点:有性能损耗
每次defer语句执行时,会把函数“压栈”,函数参数会被拷贝下来
当外层函数(非代码块,eg一个for循环)退出时,defer函数按照定义的逆序执行
如果defer执行的函数为nil,那么会在最终调用函数产生panic
defer参数
在defer函数定义时,对外部变量的引用是有两种方式的
1.作为函数参数
在defer定义时就把值传递给defer,并被cache起来
2. 作为闭包引用
在defer函数真正调用时根据整个上下文确定当前的值
闭包
闭包=函数+引用环境
go的所有匿名函数都是闭包
package mainimport "fmt"func main() {var a = Accumulator()fmt.Printf("%d\n", a(1))fmt.Printf("%d\n", a(10))fmt.Printf("%d\n", a(100))fmt.Println("------------------------")var b = Accumulator()fmt.Printf("%d\n", b(1))fmt.Printf("%d\n", b(10))fmt.Printf("%d\n", b(100)) } func Accumulator() func(int) int {var x intreturn func(delta int) int {fmt.Printf("(%+v,%+v) - ", &x, x)x += deltareturn x} }
运行结果
(0xc000016098,0) - 1 (0xc000016098,1) - 11 (0xc000016098,11) - 111 ------------------------ (0xc0000160f0,0) - 1 (0xc0000160f0,1) - 11 (0xc0000160f0,11) - 111
闭包引用了x变量,a,b可看作2个不同的实例,实例之间互不影响。实例内部,x变量是同一个地址,因此具有“累加效应”。
defer易错场景
type number
int func (n number)print(){fmt.Println(n)
}func (n*number)pprint(){fmt.Println(*n)
}func main(){var n numberdefer n.print()defer n.pprint()defer func(){n.print()}()defer func (){n.pprint()}()n = 3}
第四个defer语句是闭包,引用外部函数的n, 最终结果是3;
第三个defer语句同第四个;
第二个defer语句,n是引用,最终求值是3.
第一个defer语句,对n直接求值,开始的时候n=0, 所以最后是0;
defer拆解
return xxx
经过编译后变成三条指令
返回值 = xxx
调用defer函数
空的return
例一
package mainimport "fmt"func f() (r int) {t := 5defer func() {t = t + 5}()return t } func main() {fmt.Println(f())//5 }
拆解后
package mainimport "fmt"func f() (r int) {t := 5//1.赋值指令r = t//2.defer被插入到赋值与返回之间执行,返回值r未被修改过func() {t = t + 5}()//3.空的return指令return } func main() {fmt.Println(f()) }
例二
package mainimport "fmt"func f() (r int) {t := 5//1.赋值指令//2.这里改的t是之前传值传进去的t,不会改变要返回的tdefer func(t int) {t = t + 5}(t)//3.空的return指令return t } func main() {fmt.Println(f())//5 }
package mainimport ("errors""fmt" )func f1() {var err errordefer fmt.Println(err)err = errors.New("defer error")return } func f2() {var err errordefer func() {fmt.Println(err)}()err = errors.New("defer error")return } func f3() {var err errordefer func(err error) {fmt.Println(err)}(err)err = errors.New("defer error")return } func main() {f1()f2()f3() }
运行结果:
<nil> defer error <nil>
执行顺序 1->2->3
第1,3个函数是因为作为函数参数,定义的时候就会求值,定义的时候err变量的值都是nil, 所以最后打印的时候都是nil.
第2个函数的参数其实也是会在定义的时候求值,只不过,第2个例子中是一个闭包,它引用的变量err在执行的时候最终变成
defer error
了
defer配合recover
一次偶然的请求可能会触发某个bug, 这时用recover捕获panic, 稳住主流程,不影响其他请求。
recover()函数只在defer的上下文中才有效(且只有通过在defer中用匿名函数调用才有效),直接调用的话,只会返回
nil
.package mainimport ("fmt""os""time" )func main() {defer fmt.Println("defer main")var user = os.Getenv("USER_")go func() {defer func() {fmt.Println("defer caller")if err := recover(); err != nil {fmt.Println("recover success. err", err)}}()func() {defer func() {fmt.Println("defer here")}()if user == "" {panic("should set user env.")}//此处不会执行fmt.Println("after panic")}()}()time.Sleep(100)fmt.Println("end of main function") }
输出
defer here defer caller recover success. err should set user env. end of main function defer main
注意:
panic后的defer不会被执行(遇到panic,如果没有捕获错误,函数会立即终止)
panic没有recover时,抛出的panic到当前goroutine最上层函数时,最上层程序直接异常终止
package mainimport "fmt"func F() {defer func() {fmt.Println("b")}()panic("a") } func main() {defer func() {fmt.Println("c")}()F()fmt.Println("继续执行") } /* b c panic: a */
panic有被recover时,当前goroutine最上层函数正常执行
package mainimport "fmt"func F() {defer func() {if err := recover(); err != nil {fmt.Println("捕获异常", err)}fmt.Println("b")}()panic("a") } func main() {defer func() {fmt.Println("c")}()F()fmt.Println("继续执行") } /* 捕获异常 a b 继续执行 c */
golang——defer相关推荐
- golang defer实现
golang defer实现 1.现象 defer 会在函数return前执行 如果发生非系统级别panic,defer依然执行:在defer执行过程中,如果有recover,那就捕获panic,否则 ...
- golang defer简介 goland 警告提示 possible resource leak,difer is called in a for loop 原因
目录 警告原因 解决方法 defer理解 defer调用是一个栈结构 defer的作用域是一个函数,不是一个语句块 链式调用 针对非指针类型调用函数 警告原因 在for中使用defer关闭资源,其实资 ...
- golang defer的使用
在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内 ...
- Golang defer 快速上手
文章目录 1.简介 2.注意事项 2.1 defer 函数入参在 defer 时确定 2.2 defer 执行顺序为后进先出 2.3 defer 函数在 return 语句赋值与返回之间执行 2.4 ...
- Golang defer
在Golang使用defer常常会迷惑于以下两个问题 defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的: defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合 ...
- Golang defer解读
defer defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer语句通常用于一些成对 ...
- golang defer 关闭文件 报错file may have nil or other unexpected value as its corresponding error
错误实例: file, err := os.Open("xxx.txt") defer file.Close() if err != nil {return err } 初学者很多 ...
- golang defer使用——资源关闭时候多用
defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句.当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回.特别是当你在进行一些打开资源 ...
- [Golang]defer详解
数据结构 defer的数据结构定义在$GOROOT/src/runtime/runtime2.go // 大体定义如下,忽略少部分字段 type _defer struct {sp uintptr / ...
最新文章
- L - JavaScript继承
- 遥望星空FTP文件同步工具(附源码)1.0 发布
- 关于JVM和Dalvik的区别
- 关于memecache的使用及清楚示意
- d.php xfso_centos平台基于snort、barnyard2以及base的IDS(入侵检测系统)的搭建与测试及所遇问题汇总...
- Vue 左右翻页,点赞动画
- Spark的相关概念说明和检查点机制
- ltp-ddt realtime_cpu_load涉及的cyclictest 交叉编译
- VB 获得磁盘的文件系统
- 给出如下公式的python表达式7+9i+2xcos66_这100道练习,带你玩转Numpy
- Android学习第六天---seekbar
- Python之路 - 网络编程之Socket
- 如何从wireshark 抓包中的RTP导出 H.264 PAYLOAD,变成可用暴风直接播放的H264 裸码流文件
- 华硕主板装系统蓝屏_华硕笔记本电脑重装系统后蓝屏怎么办
- localhost改成想要的IP方法
- 带省略号的比喻句_标点符号往往能引发人们的联想,例如:“省略号像一条漫长的人生道路,等着你去书写它留下的空白。”请以一种标点符号(省略号除外)为描述对象,写一个比喻句,形象地阐发某种生活道理。...
- EN300328测试软件,蓝牙耳机EN300328测试项目。
- 计算机网络考试部分题库
- Kotlin全套视频教程分享
- Python爬虫的起点,一文轻松入门
热门文章
- 西门子快出手 华为慢接招
- iOS 7完美越狱发布 中国版存“猫腻”
- 中文分词算法及python代码实现(持续更新中)
- 牛客网算法——名企高频面试题143题(2)
- 029 怎样防止高考报考被骗
- SecureCRT输入命令没有本地回显问题
- win10系统可以做补丁服务器,win10补丁包怎么安装 win10补丁手动安装方法
- “我爱智能”原创性博客索引
- 线性规划问题的数学建模matlab,数学建模讲座之三——利用Matlab求解线性规划问题(linprog函数).ppt...
- H3C 交换机软件版本升级详细步骤