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

经过编译后变成三条指令

  1. 返回值 = xxx

  2. 调用defer函数

  3. 空的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相关推荐

  1. golang defer实现

    golang defer实现 1.现象 defer 会在函数return前执行 如果发生非系统级别panic,defer依然执行:在defer执行过程中,如果有recover,那就捕获panic,否则 ...

  2. golang defer简介 goland 警告提示 possible resource leak,difer is called in a for loop 原因

    目录 警告原因 解决方法 defer理解 defer调用是一个栈结构 defer的作用域是一个函数,不是一个语句块 链式调用 针对非指针类型调用函数 警告原因 在for中使用defer关闭资源,其实资 ...

  3. golang defer的使用

    在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内 ...

  4. Golang defer 快速上手

    文章目录 1.简介 2.注意事项 2.1 defer 函数入参在 defer 时确定 2.2 defer 执行顺序为后进先出 2.3 defer 函数在 return 语句赋值与返回之间执行 2.4 ...

  5. Golang defer

    在Golang使用defer常常会迷惑于以下两个问题 defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的: defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合 ...

  6. Golang defer解读

    defer defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer语句通常用于一些成对 ...

  7. 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 } 初学者很多 ...

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

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

  9. [Golang]defer详解

    数据结构 defer的数据结构定义在$GOROOT/src/runtime/runtime2.go // 大体定义如下,忽略少部分字段 type _defer struct {sp uintptr / ...

最新文章

  1. 怎么用python画个电脑_python语言还是java如何用python画爱心
  2. NFS服务基本配置及使用
  3. MySql——安装与配置与启动和停止
  4. 关于parallel rollback的一点总结
  5. MyBatis中增删改操作
  6. Ubuntu 14.04 AM335x TI-RTOS 编译
  7. android 回车键事件编程
  8. qdu-凑数题(01背包)
  9. 单件模式与业务逻辑服务层封装
  10. blog推荐 - Sources of Insight
  11. html5蓝牙模块,HC-05蓝牙模块介绍
  12. mysql中的事务和锁_MySQL中的事务和锁
  13. 称重仪表显示ol怎么解决_称重仪表显示Erd和数字是怎么回事?
  14. Python对excel合并单元格
  15. JVM垃圾回收(二) 垃圾回收算法
  16. QT离线安装包下载地址
  17. 主流RGB灯,灯带通用C语言程序
  18. 数字经济绿色创新匹配:全国3169公司数字金融企业绿色专利匹配数据 2011-2019年
  19. 基于阿里平头哥的单片机软、硬件i2C驱动oled
  20. vue中js转换火星坐标以及真实坐标

热门文章

  1. 小数点后保留两位有效数字(c++)
  2. 《守塔兵团》H5游戏养成玩法攻略
  3. 解决building workplace 导致的卡死,使得eclipse加速
  4. 四十岁是中年危机还是中年过渡
  5. VMThread占CPU高基本上是JVM在频繁GC导致,原因基本上是冰法下短时间内创建了大量对象堆积造成频繁GC。
  6. Why does uitableview cell remain highlighted?
  7. 战神背光键盘如何关系_全彩背光 精灵战神背光游戏键盘评测
  8. 10个现在最流行的网赚(变身富人)
  9. PHP多线程模拟秒杀抢单
  10. DEBUG:grad can be implicitly created only for scalar outputs