【go语言之defer源码分析】

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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值