45

golang defer实现原理

 5 years ago
source link: https://www.tuicool.com/articles/U77vUrb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

defer是golang提供的关键字,在函数或者方法执行完成,返回之前调用。

每次defer都会将defer函数压入栈中,调用函数或者方法结束时,从栈中取出执行,所以多个defer的执行顺序是先入后出。

defer的触发时机

官网说的很清楚:

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.

  1. 包裹着defer语句的函数返回时
  2. 包裹着defer语句的函数执行到最后时
  3. 当前goroutine发生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执行时被声明,有名返回值则是在函数声明的同时被声明,因此在defer语句中只能访问有名返回值,而不能直接访问匿名返回值。所以f1的结果是6。f2是有名返回值,defer函数修改了f2的返回值。f3是有名返回值,但是因为r是作为defer的传参,所以defer对r的修改也不会影响调用函数的返回值。

defer源码解析

defer的实现源码是在runtime.deferproc

然后在函数返回之前的地方,运行函数runtime.deferreturn。

先了解defer结构体:

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(&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 < uintptr(len(p{}.deferpool)) {
                    pp := gp.m.p.ptr()
                    if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
                            // Take the slow path on the system stack so
                            // we don't grow newdefer's stack.
                            systemstack(func() {
                                    lock(&sched.deferlock)
                                    for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
                                            d := sched.deferpool[sc]
                                            sched.deferpool[sc] = d.link
                                            d.link = nil
                                            pp.deferpool[sc] = append(pp.deferpool[sc], d)
                                    }
                                    unlock(&sched.deferlock)
                            })
                    }
                    if n := len(pp.deferpool[sc]); n > 0 {
                            d = pp.deferpool[sc][n-1]
                            pp.deferpool[sc][n-1] = nil
                            pp.deferpool[sc] = pp.deferpool[sc][:n-1]
                    }
            }
            if d == nil {
                    // Allocate new defer+args.
                    systemstack(func() {
                            total := roundupsize(totaldefersize(uintptr(siz)))
                            d = (*_defer)(mallocgc(total, deferType, true))
                    })
                    if debugCachedWork {
                            // Duplicate the tail below so if there's a
                            // crash in checkPut we can tell if d was just
                            // allocated or came from the pool.
                            d.siz = siz
                            d.link = gp._defer
                            gp._defer = d
                            return d
                    }
            }
            d.siz = siz
            d.link = gp._defer
            gp._defer = d
            return d
    }

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


Recommend

  • 77

    #1 — Deferred nil funcIf a deferred func evaluates to nil, execution panics when the surrounding func ends not when defer...

  • 62
    • studygolang.com 6 years ago
    • Cache

    golang 中的闭包和defer

    golang中的defer和闭包对很多初学者来说,有时候有很多坑,但是很多介绍的文章有写的乱七八糟.放假了没事可干,就稍微总结一下. 闭包 闭包有叫匿名函数,使用闭包可以使我们的代码更加优雅简洁,顾名思义匿名函数就是没有...

  • 23
    • studygolang.com 6 years ago
    • Cache

    [译] part 29: golang defer

    原文地址: Part 29: Defer 原文作者:

  • 42

    README.md PHP Defer

  • 14
    • studygolang.com 5 years ago
    • Cache

    Golang中的Defer必掌握的7知识点

    在用Golang开发的时候, defer 这个语法也是必备的知识,但是我们除了知道他是在一个函数退出之前执行,对于 defer 是否还有其他地方需要注意的呢。 本文整理的 defer 的全场景使用情...

  • 12

    go1.14实现defer性能大幅度提升原理 – 峰云就她了 峰云就她了 专注于Golang、Python、DB、cluster go1.14实现defer性能大幅度提升原理

  • 8

    Golang中defer、return、返回值之间执行顺序的坑 henrylee2cn · 2015-09-13 16:00:00 · 14064 次点击 · 预计阅读时间 2 分钟 · 不到1分钟之前 开始浏览    ...

  • 4
    • www.luozhiyun.com 3 years ago
    • Cache

    深入 Go 语言 defer 实现原理

    深入 Go 语言 defer 实现原理 Posted on 2021年5月1日2021年5月1日 by luozhiyun 转载请声明出处哦~,本篇文章发布于luozhiyun的博客:

  • 4
    • studygolang.com 3 years ago
    • Cache

    Go defer 原理和源码剖析

    Go 语言中有一个非常有用的保留字 defer,defer 语句可以调用一个函数,该函数的执行被推迟到包裹它的函数返回时执行。 defer 语句调用的函数,要么是因为包裹它的函数执行了 return 语句,到达了函数体的末端,要么是因为对应的 goroutine 发生了 panic...

  • 10
    • tyloafer.github.io 3 years ago
    • Cache

    深入理解Go-defer的原理剖析

    Defer 也是Go里面比较特别的一个关键字了,主要就是用来保证在程序执行过程中,defer后面的函数都会被执行到,一般用来关闭连接、清理资源等。 defertype _defer struct { siz int32 // 参数的大小 star...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK