登录  /  注册
首页 > 后端开发 > Golang > 正文

一文详解golang defer的实现原理

藏色散人
发布: 2021-09-09 15:22:58
转载
2419人浏览过

本文由go语言教程栏目给大家介绍golang defer实现原理,希望对需要的朋友有所帮助!

defer是golang提供的关键字,在函数或者方法执行完成,返回之前调用。
每次defer都会将defer函数压入栈中,调用函数或者方法结束时,从栈中取出执行,所以多个defer的执行顺序是先入后出。

for i := 0; i <p><strong>defer的触发时机</strong></p><p>官网说的很清楚:<br>A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.</p><ol>
<li>包裹着defer语句的函数返回时</li>
<li>包裹着defer语句的函数执行到最后时</li>
<li>
<p>当前goroutine发生Panic时</p>
<pre class="brush:php;toolbar:false">    //输出结果:return前执行defer
   func f1() {
       defer fmt.Println("return前执行defer")
       return 
   }

   //输出结果:函数执行
   // 函数执行到最后
   func f2() {
       defer fmt.Println("函数执行到最后")
       fmt.Println("函数执行")
   }

   //输出结果:panic前  第一个defer在Panic发生时执行,第二个defer在Panic之后声明,不能执行到
   func f3() {
       defer fmt.Println("panic前")
       panic("panic中")
       defer fmt.Println("panic后")
   }
登录后复制

defer,return,返回值的执行顺序

先来看3个例子

func f1() int { //匿名返回值
        var r int = 6
        defer func() {
                r *= 7
        }()
        return r
}

func f2() (r int) { //有名返回值
        defer func() {
                r *= 7
        }()
        return 6
}

func f3() (r int) { //有名返回值
    defer func(r int) {
        r *= 7
    }(r)
    return 6
}
登录后复制

f1的执行结果是6, f2的执行结果是42,f3的执行结果是6
在golang的官方文档里面介绍了,return,defer,返回值的执行顺序:
if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller.

1. 先给返回值赋值
2. 执行defer语句
3. 包裹函数return返回

f1的结果是6。f1是匿名返回值,匿名返回值是在return执行时被声明,因此defer声明时,还不能访问到匿名返回值,defer的修改不会影响到返回值。
f2先给返回值r赋值,r=6,执行defer语句,defer修改r, r = 42,然后函数return。
f3是有名返回值,但是因为r是作为defer的传参,在声明defer的时候,就进行参数拷贝传递,所以defer只会对defer函数的局部参数有影响,不会影响到调用函数的返回值。

闭包与匿名函数
匿名函数:没有函数名的函数。
闭包:可以使用另外一个函数作用域中的变量的函数。

for i := 0; i <p><strong>defer源码解析</strong><br>defer的实现源码是在runtime.deferproc<br>然后在函数返回之前的地方,运行函数runtime.deferreturn。<br>先了解defer结构体:</p><pre class="brush:php;toolbar:false">    type _defer struct {
            siz     int32 
            started bool
            sp      uintptr // sp at time of defer
            pc      uintptr
            fn      *funcval
            _panic  *_panic // panic that is running defer
            link    *_defer
    }
登录后复制

sp 和 pc 分别指向了栈指针和调用方的程序计数器,fn是向 defer 关键字中传入的函数,Panic是导致运行defer的Panic。
每遇到一个defer关键字,defer函数都会被转换成runtime.deferproc
deferproc通过newdefer创建一个延迟函数,并将这个新建的延迟函数挂在当前goroutine的_defer的链表上

    func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
            sp := getcallersp()
            argp := uintptr(unsafe.Pointer(&amp;fn)) + unsafe.Sizeof(fn)
            callerpc := getcallerpc()

            d := newdefer(siz)
            if d._panic != nil {
                    throw("deferproc: d.panic != nil after newdefer")
            }
            d.fn = fn
            d.pc = callerpc
            d.sp = sp
            switch siz {
            case 0:
                    // Do nothing.
            case sys.PtrSize:
                    *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
            default:
                    memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
            }
            return0()
    }
登录后复制

newdefer会先从sched和当前p的deferpool取出一个_defer结构体,如果deferpool没有_defer,则初始化一个新的_defer。
_defer是关联到当前的g,所以defer只对当前g有效。
d.link = gp._defer
gp._defer = d //用链表连接当前g的所有defer

    func newdefer(siz int32) *_defer {
            var d *_defer
            sc := deferclass(uintptr(siz))
            gp := getg()
            if sc  0 {
                            d = pp.deferpool[sc][n-1]
                            pp.deferpool[sc][n-1] = nil
                            pp.deferpool[sc] = pp.deferpool[sc][:n-1]
                    }
            }
            ......
            d.siz = siz
            d.link = gp._defer
            gp._defer = d
            return d
    }
登录后复制

deferreturn 从当前g取出_defer链表执行,每个_defer调用freedefer释放_defer结构体,并将该_defer结构体放入当前p的deferpool中。

defer性能分析
defer在开发中,对于资源的释放,捕获Panic等很有用处。可以有些开发者没有考虑过defer对程序性能的影响,在程序中滥用defer。
在性能测试中可以发现,defer对性能还是有一些影响。雨痕的Go 性能优化技巧 4/1,对defer语句带来的额外开销有一些测试。

测试代码

    var mu sync.Mutex
    func noDeferLock() {
        mu.Lock()
        mu.Unlock()
    }   

    func deferLock() {
        mu.Lock()
        defer mu.Unlock()
    }          
    
    func BenchmarkNoDefer(b *testing.B) {
        for i := 0; i <p><strong>测试结果:</strong></p><pre class="brush:php;toolbar:false">    BenchmarkNoDefer-4      100000000               11.1 ns/op
    BenchmarkDefer-4        36367237                33.1 ns/op
登录后复制

通过前面的源码解析可以知道,defer会先调用deferproc,这些都会进行参数拷贝,deferreturn还会提取相关信息延迟执行,这些都是比直接call一条语句消耗更大。

defer性能不高,每次defer耗时20ns,,在一个func内连续出现多次,性能消耗是20ns*n,累计出来浪费的cpu资源很大的。

解决之道:除了需要异常捕获时,必须使用defer;其它资源回收类defer,可以判断失败后,使用goto跳转到资源回收的代码区。对于竞争资源,可以在使用完之后,立马释放资源,这样才能最优的使用竞争资源。

更多golang相关知识,请访问golang教程栏目!

以上就是一文详解golang defer的实现原理的详细内容,更多请关注php中文网其它相关文章!

智能AI问答
PHP中文网智能助手能迅速回答你的编程问题,提供实时的代码和解决方案,帮助你解决各种难题。不仅如此,它还能提供编程资源和学习指导,帮助你快速提升编程技能。无论你是初学者还是专业人士,AI智能助手都能成为你的可靠助手,助力你在编程领域取得更大的成就。
相关标签:
来源:segmentfault网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
最新问题
关于CSS思维导图的课件在哪? 课件
凡人来自于2024-04-16 10:10:18
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2024 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号