golang defer实现
1.现象
- defer 会在函数return前执行
- 如果发生非系统级别panic,defer依然执行;在defer执行过程中,如果有recover,那就捕获panic,否则defer链调用结束,协程就GG
- 如果发生系统级别异常(如panic during malloc)则直接退出进程不会执行任何defer函数
- 多个defer倒序执行,类似栈
- defer语句的参数,是立即评估,而不是等到执行时才评估
2.实现
- defer是编辑器通过在函数开始、结束处插入特殊代码实现的,纯编译期行为
我们写的函数如下:
func A() {
defer B()
// A_logic_code
}
编译器给我们偷偷插入了2行(实际不止哈,简要示意)
func A() {
r := deferproc(8, B) // 8是什么先不管,总之记住,这里叫注册defer
// A_logic_code
runtime.deferreturn() // 调用defer链
return
}
通过这个示意伪代码,可以很清晰理解现象1、现象4
8 是函数B的参数和返回值空间大小之和,用于在A的栈上分配空间给defer
-
再说现象3
多个defer的时候,依次执行,每次都将一个defer结构体插入到defer链的链表头,所以执行的时候,就是倒序了
-
再说非系统级别panic
- 总原则:先触发非系统级别panic函数的执行,然后gopanic函数去调用defer函数,此时会一路传递panic信息,panic不会阻断defer执行
- 如果一路在延迟函数中没有recover函数的调用,则会到达该携程的起点,该携程结束
-
再说一个特例,panic1触发defer1函数,defer1函数有产生新的panic2,panic2又触发defer2函数,defer2函数又产生新的panic3
Go的机制是如果当前defer函数是由之前的panic触发的并且当前defer函数会触发新的panic,则前一个panic停止执行,意思是本例,最终只能捕获到panic3,前两个被覆盖吞掉了
3.性能问题和优化
- go1.12问题
- defer在堆上分配,执行的时候,拷贝回栈
- defer链表本就低效(执行过程中可能产生新的defer insert,执行时又delete)
-
go1.13减少堆栈拷贝
-
把defer结构体定义在原函数内部,分配在栈上,然后注册的时候,指向栈内地址
defer结构体增加一个字段heap标识是否为堆分配
-
不适用于显示、隐式循环defer
-
提升 约30%
-
-
go1.14优化
-
函数内联展开:更狠,不再注册defer链,而是直接开始处插入defer函数所需的参数的变量定义,return插入显示defer函数的代码
简单来说,就是直接把defer函数的逻辑代码拷贝,嵌套到原函数,再适当的补齐defer函数执行所需的形参
-
不适用于显示、隐式循环defer
-
提升一个数量级
-