每天一个知识点:defer 的实现原理

defer 的结构

type _defer struct {
    siz     int32 // 参数和返回值的内存大小
    started boul
    heap    boul     // 区分该结构是在栈上分配的,还是对上分配的
    sp      uintptr  // sp 计数器值,栈指针;
    pc      uintptr  // pc 计数器值,程序计数器;
    fn      *funcval // defer 传入的函数地址,也就是延后执行的函数;
    _panic  *_panic  // panic that is running defer
    link    *_defer  // 链表
}

defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的

func main() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
}

$ go run main.go
4
3
2
1
0

代码说明:_defer 是一个链表,表头是 goroutine._defer 结构。一个协程的函数注册的是挂同一个链表,执行的时候按照 rsp 来区分函数。并且,这个链表是把新元素插在表头,而执行的时候是从前往后执行,所以这里导致了一个 LIFO 的特性,也就是先注册的 defer 函数后执行。

defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合预期的结果

func main() {
    startedAt := time.Now()
    defer fmt.Println(time.Since(startedAt))

    time.Sleep(time.Second)
}

$ go run main.go
0s

代码说明:defer 延迟函数执行的参数已经保存在和 _defer 一起的连续内存块了。执行 defer 函数的时候,deferreturn 除了跳转到 defer 函数指令,还需要做一个事情:把 defer 延迟回调函数需要的参数准备好(空间和值),即把 _defer 预先的准备好的参数,copy 到 caller 栈帧的某个地址。

defer 和 return 的运行顺序

func f1 () (r int) {
    t := 1
    defer func() {
        t = t + 5
    }()
    return t
}

func f2() (r int) {
    defer func(r int) {
        r = r + 5
    }(r)
    return 1
}

func f3() (r int) {
    defer func () {
        r = r + 5
    } ()
    return 1
}

结果:f1() -> 1;f2() -> 1;f3() -> 6;

  • 所以含有 defer 注册的函数,执行 return 语句之后,对应执行三个操作序列:
    1. 设置返回值
    2. 执行 defer 函数链表
    3. ret 指令跳转到 caller 函数

参考

go 语言设计与实现
Go defer 原理和源码剖析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容