golang defer

golang defer

defer是golang的一个特色功能,defer用来声明一个延迟函数,把这个函数类似于放到一个栈上,在方法return之前触发调用。我们经常用他来做一些资源的释放,比如关闭io操作。
defer的具体规则:

  1. 延迟函数的参数在defer语句出现时就已经确定下来了。
  2. 延迟函数执行按后进先出顺序执行,即先出现的defer后执行(类似于栈);
  3. 延迟函数可能操作主函数的具名返回值;
  4. 即使出现panic,defer函数也会正常执行。

解释:

  1. defer语句中的参数值在defer出现时就已经确定下来,实际上是拷贝了一份。后面对变量的修改不会影响函数的执行。注意:对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。
  2. 定义defer类似于入栈操作,执行defer类似于出栈操作。设计defer的初衷是简化函数返回时资源清理的动作,资源往往有依赖顺序,比如先申请资源A,再根据A资源申请B资源,再根据资源B申请资源C,即申请顺序是:A->B->C,释放时往往又要反向进行,这就是把deffer设计成FIFO的原因。每申请到一个用完需要释放的资源时,立即定义一个defer来释放资源是个很好的习惯。
  3. 定义defer的函数,即主函数可能有返回值,返回值没有名字没有关系,defer所作用的函数,即延迟函数可能会影响返回值。首先必须要了解:return关键字不是一个原子操作,实际上return只代表汇编指令ret,表示程序即将跳转执行。return i,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。这里还要分几种情况来看:
    • 第一:主函数为匿名返回值,return返回的是字面量,这种情况,defer无法修改返回值;
    • 第二:主函数为匿名返回值,return返回的是本地或局部变量,这种情况defer可以引用到返回值,但是不会影响返回值,因为return之前的返回值实际上是存到了栈上,然后defer只是拷贝了一份,当然如果return的是指针,情况就不一样了。defer可以修改指针指向的值。
    • 第三:主函数为命名(具名)返回值,主函数中声明带名字的返回值,会初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值,如果defer语句操作该返回值,可能会改变返回结果。
  4. 为什么panic了defer函数也会执行,这个需要结合defer函数的实现来说了。

defer数据结构:

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

defer后面一定要接一个函数,所以defer的数据结构和一般函数类似,也有栈指针、程序计数器、函数地址等等。
与函数不同的一点是它含有一个指针,可用于指向另一个defer,每个goroutine数据结构中实际上也有一个defer指针,该指针指向一个defer的链表,每次声明一个defer时就将defer插入到单链表表头,每次执行defer就从单链表表头取出一个defer执行。

defer的创建和执行:
源码包src/runtime/panic.go定义了两个方法分别用于创建defer和执行defer:

  • deferproc(): 在声明defer处调用,将其defer函数存入goroutine的链表中;
  • deferreturn(): 在return指令,准确的讲是在ret指令前调用,将其defer从goroutine链表中取出并执行;

可以这么理解,在编译阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn()。

总结:

  1. defer定义的延迟函数参数在defer语句定义时就已经确定下来了;
  2. defer定义顺序与实际执行顺序相反;
  3. return不是原子操作,执行过程是:保存返回值(若有)–>执行defer(若有)–>执行ret跳转;
  4. 申请资源后立即使用defer关闭资源是好习惯。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值