defer
首先来看一下官方的解析:
- A deferred function’s arguments are evaluated when the defer statement is evaluated.
- Deferred function calls are executed in Last In First Out order after the surrounding function returns.
- Deferred functions may read and assign to the returning function’s named return values.
总结:
规则一:延迟函数的参数在defer语句出现时就已经确定下来了
规则二:延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
规则三:延迟函数可能操作主函数的具名返回值
数据结构
源码包src/src/runtime/runtime2.go:_defer定义了defer的数据结构:
started bool
heap bool
// openDefer indicates that this _defer is for a frame with open-coded
// defers. We have only one defer record for the entire frame (which may
// currently have 0, 1, or more defers active).
openDefer bool
sp uintptr // sp at time of defer
pc uintptr // pc at time of defer
fn func() // can be nil for open-coded defers
_panic *_panic // panic that is running defer
link *_defer // next defer on G; can point to either heap or stack!
// If openDefer is true, the fields below record values about the stack
// frame and associated function that has the open-coded defer(s). sp
// above will be the sp for the frame, and pc will be address of the
// deferreturn call in the function.
fd unsafe.Pointer // funcdata for the function associated with the frame
varp uintptr // value of varp for the stack frame
// framepc is the current pc associated with the stack frame. Together,
// with sp above (which is the sp associated with the stack frame),
// framepc/sp can be used as pc/sp pair to continue a stack trace via
// gentraceback().
framepc uintptr
- sp 和 pc 分别代表栈指针和调用方的程序计数器;
- fn 是 defer 关键字中传入的函数;
- _panic 是触发延迟调用的结构体,可能为空;
- openDefer 表示当前 defer 是否经过开放编码的优化;
我们知道defer后面一定要接一个函数的,所以defer的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer时就从单链表表头取出一个defer执行。
下图展示多个defer被链接的过程:
新声明的defer总是添加到链表头部,函数返回前执行defer则是从链表首部依次取出执行。
一个goroutine可能连续调用多个函数,defer添加过程跟上述流程一致,进入函数时添加defer,离开函数时取出defer,所以即便调用多个函数,也总是能保证defer是按FIFO方式执行的。
defer的创建和执行
源码包src/runtime/panic.go定义了两个方法分别用于创建defer和执行defer。
- deferproc(): 在声明defer处调用,其将defer函数存入goroutine的链表中;
- deferreturn():在return指令,准确的讲是在ret指令前调用,其将defer从goroutine链表中取出并执行。
可以简单这么理解,在编译在阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。
defer和return执行顺序
return执行顺序
先来看看return的执行顺序:
return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转。
即return是将i值存入栈中作为返回值,然后执行跳转。
那return又分为匿名返回值和有名返回值,这两种情况又有一些区别。
有名返回值
package main
import "fmt"
func main() {
fmt.Println(foo())
}
func foo() (ret int) {
defer func() {
ret++
}()
return 0
}
// 输出
// 1
首先,return在最后返回了0,注意了,这时候会执行第一步,**ret=0**
,
在完成赋值后,执行存在的defer,在defer中,我们又执行了ret++
,所以ret的值变为了1
所以在最终 foo()
函数返回值为1
匿名返回值
package main
import "fmt"
func main() {
fmt.Println(foo())
}
func foo() int {
var i int
defer func() {
i++
}()
return 1
}
// 输出
// 1
上面的return语句,直接把1写入栈中作为返回值,延迟函数无法操作该返回值,所以就无法影响返回值。
所以结论是:第一步先return赋值,第二步再执行defer,第三步执行return返回。
defer与panic
总结
- defer定义的延迟函数参数在defer语句出时就已经确定下来了
- defer定义顺序与实际执行顺序相反
- return不是原子操作,执行过程是: 保存返回值(若有)-->执行defer(若有)-->执行ret跳转
- 申请资源后立即使用defer关闭资源是好习惯