go语言之defer
defer 是go语言中的关键字,可以在函数结束之前执行方法。
因为不同的版本实现不同,当前的版本是1.19.1.
先看一个最简单的defer使用
package main
import "fmt"
func main() {
defer fmt.Println("2")
fmt.Println("1")
}
运行结果也是先1,后2
然后看一下这段代码的反编译。
可以看到,只有一个deferreturn。在go的1.13和1.14会有deferproc或者deferstack,当然在 循环中,或者在 隐式循环中还是需要在堆上面分配,这个后面说。
当然为什么没有看到创建_defer,是因为这个已经在栈上分配好了?在反编译中没看到具体的创建。
然后主要看一下_derfer这个结构体.
_defer
看一下源码中的定义
// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in deferProcStack.
// This struct must match the code in cmd/compile/internal/ssagen/ssa.go:deferstruct
// and cmd/compile/internal/ssagen/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
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 // 是否是开放的 也就是把defer内容放到return 免于创建_defer
sp uintptr // sp at time of defer 栈指针 判断是否是当前的defer
pc uintptr // pc at time of defer 返回值地址
fn func() // can be nil for open-coded defers 执行的函数地址
_panic *_panic // panic that is running defer panic内容的链表
link *_defer // next defer on G; can point to either heap or stack! // 下一个需要执行的_defer
// 下面都是和栈扫描相关联的
// 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
}
deferreturn
再看一下这个方法的定义。
// deferreturn runs deferred functions for the caller's frame.
// The compiler inserts a call to this at the end of any
// function which calls defer.
func deferreturn() {
// 获取当前goroutine
gp := getg()
// 依次获取当前的_defer链表
for {
d := gp._defer
if d == nil {
return
}
// 获取当前栈指针 是否是当前defer
sp := getcallersp()
if d.sp != sp {
return
}
// 是都是opencoeded defer
if d.openDefer {
done := runOpenDeferFrame(gp, d)
if !done {
throw("unfinished open-coded defers in deferreturn")
}
// 链表的下一个放到_defer中
gp._defer = d.link
freedefer(d)
// If this frame uses open defers, then this
// must be the only defer record for the
// frame, so we can just return.
return
}
// 获取方法
fn := d.fn
// 方法制空
d.fn = nil
// 链表的下一个放到_defer中
gp._defer = d.link
// 释放
freedefer(d)
// 调用传入的方法
fn()
}
}
简单看一下freedefer,这个是为了减少堆上面的分配而创建的defer池,看一下实现。
// Free the given defer.
// The defer cannot be used after this call.
//
// This is nosplit because the incoming defer is in a perilous state.
// It's not on any defer list, so stack copying won't adjust stack
// pointers in it (namely, d.link). Hence, if we were to copy the
// stack, d could then contain a stale pointer.
//
//go:nosplit
func freedefer(d *_defer) {
// 指针设置为nil
d.link = nil
// After this point we can copy the stack.
// panic必须为空
if d._panic != nil {
freedeferpanic()
}
// 判断方法是否存在
if d.fn != nil {
freedeferfn()
}
// 是否在栈上分配那么直接返回
if !d.heap {
return
}
// 获取M 和 P
mp := acquirem()
pp := mp.p.ptr()
//
if len(pp.deferpool) == cap(pp.deferpool) {
// Transfer half of local cache to the central cache.
var first, last *_defer
// 控制数量在一半以下
for len(pp.deferpool) > cap(pp.deferpool)/2 {
n := len(pp.deferpool)
d := pp.deferpool[n-1]
pp.deferpool[n-1] = nil
pp.deferpool = pp.deferpool[:n-1]
if first == nil {
first = d
} else {
last.link = d
}
last = d
}
lock(&sched.deferlock)
last.link = sched.deferpool
sched.deferpool = first
unlock(&sched.deferlock)
}
*d = _defer{}
// 放到池子中
pp.deferpool = append(pp.deferpool, d)
// 释放
releasem(mp)
mp, pp = nil, nil
}
然后看一下通过dlv来debug 方法deferreturn,拦一下defer的具体的每个对象。
可以看出来,started,heap,openDefer这三个都是false,然后sp是栈指针,pc是返回地址,fn就是在main函数中的闭包函数,这是这里给了一个名字叫func1。
可以看到这里的link是空,我们在代码中再复制一个defer看看,如下
可以看出这个link又链接了下一个defer,因为defer先进后出,所以第一个fn是func2,第二个是func1.
然后看一下在堆上分配的情况,这个时候需要在循环中去创建defer,如下:
package main
import "fmt"
func main() {
for i := 0; i < 2; i++ {
defer func() {
fmt.Println("a")
}()
}
}
然后看一下创建的反汇编代码
可以看到调用了deferproc方法,然后调用了两次deferreturn。这个deferproc就是在堆上分配_defer这个结构体。
deferproc
看一下这个方法的源码
// Create a new deferred function fn, which has no arguments and results.
// The compiler turns a defer statement into a call to this.
func deferproc(fn func()) {
// 获取当前goroutine
gp := getg()
if gp.m.curg != gp {
// go code on the system stack can't defer
throw("defer on system stack")
}
// 实例化_defer 从堆上分配 这个从go的池子里面获取
d := newdefer()
if d._panic != nil {
throw("deferproc: d.panic != nil after newdefer")
}
// 分配变量
d.link = gp._defer
gp._defer = d
d.fn = fn
// 分配栈指针和返回地址
d.pc = getcallerpc()
// We must not be preempted between calling getcallersp and
// storing it to d.sp because getcallersp's result is a
// uintptr stack pointer.
d.sp = getcallersp()
// deferproc returns 0 normally.
// a deferred func that stops a panic
// makes the deferproc return 1.
// the code the compiler generates always
// checks the return value and jumps to the
// end of the function if deferproc returns != 0.
return0()
// No code can go here - the C return register has
// been set and must not be clobbered.
}
通过看出来这个heap是true。