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 / ...
最新文章
- 怎么用python画个电脑_python语言还是java如何用python画爱心
- NFS服务基本配置及使用
- MySql——安装与配置与启动和停止
- 关于parallel rollback的一点总结
- MyBatis中增删改操作
- Ubuntu 14.04 AM335x TI-RTOS 编译
- android 回车键事件编程
- qdu-凑数题(01背包)
- 单件模式与业务逻辑服务层封装
- blog推荐 - Sources of Insight
- html5蓝牙模块,HC-05蓝牙模块介绍
- mysql中的事务和锁_MySQL中的事务和锁
- 称重仪表显示ol怎么解决_称重仪表显示Erd和数字是怎么回事?
- Python对excel合并单元格
- JVM垃圾回收(二) 垃圾回收算法
- QT离线安装包下载地址
- 主流RGB灯,灯带通用C语言程序
- 数字经济绿色创新匹配:全国3169公司数字金融企业绿色专利匹配数据 2011-2019年
- 基于阿里平头哥的单片机软、硬件i2C驱动oled
- vue中js转换火星坐标以及真实坐标
热门文章
- 小数点后保留两位有效数字(c++)
- 《守塔兵团》H5游戏养成玩法攻略
- 解决building workplace 导致的卡死,使得eclipse加速
- 四十岁是中年危机还是中年过渡
- VMThread占CPU高基本上是JVM在频繁GC导致,原因基本上是冰法下短时间内创建了大量对象堆积造成频繁GC。
- Why does uitableview cell remain highlighted?
- 战神背光键盘如何关系_全彩背光 精灵战神背光游戏键盘评测
- 10个现在最流行的网赚(变身富人)
- PHP多线程模拟秒杀抢单
- DEBUG:grad can be implicitly created only for scalar outputs