Go defer原理分析

本文详细解析了Go语言中的defer语句,包括它的作用、执行顺序、延迟函数的行为规则,以及在不同返回情况下的影响。defer常用于资源释放和流程控制,其执行过程涉及堆栈操作和goroutine的数据结构。文章还探讨了堆defer、栈defer和开放编码类型的defer的特性和限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

defer

用于延迟函数的调用,常用于关闭文件或者关闭锁的场景。
defer语句采用类似栈的方式,每遇到一个defer就会把defer后面的函数压入栈中,在函数返回前再把栈中的函数依次取出执行。
一般函数正常返回时会执行被defer延迟的函数,特别的遇到return和panic时也会触发延迟函数

defer作用于资源释放(关闭文件句柄、数据库连接、停止定时器ticker以及关闭管道)、流程控制(控制函数执行顺序,如wait.Group)和异常处理(recover()),但是defer关键字只能作用于函数或者函数调用。

三条defer的行为规则:

  1. 延迟函数的参数在defer语句出现时就已经确定了
  2. 延迟函数按后进先出LIFO的顺序执行,即先出现的defer最后执行
  3. 难点 延迟函数可能操作住函数的具体变量名称返回值,4种情况依次讲解:

(1). 函数返回过程

	func deferFuncReturn() (res int) {
		i := 1
		defer func() {
			res++
		}()
		return i
	}

需要知道的是return不是一个原子操作,其分为两步执行,先将i放入栈中作为返回值,然后再进行跳转,而defer的执行正好是在跳转之前,所以defer执行时是有机会操作返回值的。
return语句可以翻译成

	res = i
	return 

defer是在return之前,所以就有如下变化

	res = 1
	res++
	return
	所以结果就是i++

(2). 函数有匿名返回值,返回字面量, 这种情况defer是无法操作返回值的。

	func foo() int{
		var i int
		defer func(){
			i++
		}()
		return 1
	}
	这里是直接返回1

(3). 主函数有匿名返回值,返回变量, 这种情况下defer语句可以引用返回值,但是不会改变返回值

	func foo() int {
		var i int
		defer func(){
			i++
		}()
		return i
	}
	i在defer之前就会赋值给返回值变量,defer会修改i,但不会修改返回值变量。

(4). 主函数有具体的返回值变量,主函数声明语句中带名字的返回值会被初始化一个局部变量,函数内部可以像使用局部变量一样使用该返回值,如果defer操作该返回值,有可能会改变返回结果。

	func foo() (ret int){
		defer func(){
			ret++
		}
		return 0
	}
	这里就好理解了,返回1
	ret = 0
	ret++
	return

下面来看defer的数据结构

type _defer struct{
	sp uintptr		// 函数栈指针
	pc uintptr		// 程序计数器
	fn *funcval		// 函数地址
	link *_defer		// 用于链接多个defer
}

每个_defer实例是对一个函数的封装,编译器会把每一个延迟函数编译成一个_defer实例暂存到goroutine数据结构中,待函数结束时再逐个取出执行。
多个_defer实例使用指针link链接成一个单链表,保存到goroutine中,下面是goroutine结构中关于_defer的部分

type g struct {
	_defer  *_defer		// defer链表
}

每次插入_defer实例都是从链表的头部插入,函数执行结束再依次从头部取出defer执行。

defer的创建和执行
创建defer: deferproc() 将defer函数处理成_defer实例,并加入goroutine的链表中;
执行defer: deferreturn() 将defer从goroutine中取出并执行。

整个流程就是:编译器在编译阶段把defer语句替换成函数deferproc(),在return前插入函数deferreturn(), 每次执行deferproc()都会创建一个运行时_defer实例并存储,函数返回前执行deferreturn()依次拿出_defer实例并执行。

最后再简述一下堆defer、栈defer和开放编码类型的defer

堆defer主要的问题在于频繁的对内存分配及释放,导致性能较差。
栈defer由于栈空间有限,不能把所有的defer都放在栈中。
开放编码类型的defer 编译器将defer语句直接翻译成相应的执行代码插入到函数的尾部,从而节省了_defer节点转储的代价,这样就使得延迟函数和普通函数一样调用即可,只需要关注执行。
当然也是有限制条件的:

  1. 编译时禁止使用编译器优化;
  2. defer出现在循环语句中;
  3. 单个函数中defer出现8个以上,或者return个数和defer的个数乘积超过15。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hewesH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值