【golang】defer详解

去去1002

特性

我们简单的过一下 defer 关键字的基础使用,让大家先有一个基础的认知

一、延迟调用

func main() {
    defer log.Println("EDDYCJY.")

    log.Println("end.")
}

输出结果:

$ go run main.go
2019/05/19 21:15:02 end.
2019/05/19 21:15:02 EDDYCJY.

二、后进先出

func main() {
    for i := 0; i < 6; i++ {
        defer log.Println("EDDYCJY" + strconv.Itoa(i) + ".")
    }


    log.Println("end.")
}

输出结果:

$ go run main.go
2019/05/19 21:19:17 end.
2019/05/19 21:19:17 EDDYCJY5.
2019/05/19 21:19:17 EDDYCJY4.
2019/05/19 21:19:17 EDDYCJY3.
2019/05/19 21:19:17 EDDYCJY2.
2019/05/19 21:19:17 EDDYCJY1.
2019/05/19 21:19:17 EDDYCJY0.

三、运行时间点

func main() {
    func() {
         defer log.Println("defer.EDDYCJY.")
    }()

    log.Println("main.EDDYCJY.")
}

输出结果:

$ go run main.go
2019/05/22 23:30:27 defer.EDDYCJY.
2019/05/22 23:30:27 main.EDDYCJY.

四、异常处理

func main() {
    defer func() {
        if e := recover(); e != nil {
            log.Println("EDDYCJY.")
        }
    }()

    panic("end.")
}

输出结果:

$ go run main.go
2019/05/20 22:22:57 EDDYCJY.

源码剖析

$ go tool compile -S main.go
"".main STEXT size=163 args=0x0 locals=0x40
    ...
    0x0059 00089 (main.go:6)    MOVQ    AX, 16(SP)
    0x005e 00094 (main.go:6)    MOVQ    $1, 24(SP)
    0x0067 00103 (main.go:6)    MOVQ    $1, 32(SP)
    0x0070 00112 (main.go:6)    CALL    runtime.deferproc(SB)
    0x0075 00117 (main.go:6)    TESTL    AX, AX
    0x0077 00119 (main.go:6)    JNE    137
    0x0079 00121 (main.go:7)    XCHGL    AX, AX
    0x007a 00122 (main.go:7)    CALL    runtime.deferreturn(SB)
    0x007f 00127 (main.go:7)    MOVQ    56(SP), BP
    0x0084 00132 (main.go:7)    ADDQ    $64, SP
    0x0088 00136 (main.go:7)    RET
    0x0089 00137 (main.go:6)    XCHGL    AX, AX
    0x008a 00138 (main.go:6)    CALL    runtime.deferreturn(SB)
    0x008f 00143 (main.go:6)    MOVQ    56(SP), BP
    0x0094 00148 (main.go:6)    ADDQ    $64, SP
    0x0098 00152 (main.go:6)    RET
    ...

首先我们需要找到它,找到它实际对应什么执行代码。通过汇编代码,可得知涉及如下方法:

  • runtime.deferproc
  • runtime.deferreturn

很显然是运行时的方法,是对的。我们继续往下走看看都分别承担了什么行为

数据结构

在开始前我们需要先介绍一下 defer 的基础单元 _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
}

...
type funcval struct {
    fn uintptr
    // variable-size, fn-specific data here
}

  • siz:所有传入参数的总大小
  • started:该 defer 是否已经执行过
  • sp:函数栈指针寄存器,一般指向当前函数栈的栈顶
  • pc:程序计数器,有时称为指令指针(IP),线程利用它来跟踪下一个要执行的指令。在大多数处理器中,PC 指向的是下一条指令,而不是当前指令
  • fn:指向传入的函数地址和参数
  • \_panic:指向 _panic 链表
  • link:指向 _defer 链表

image

deferproc,预处理defer函数,存储到结构中,供后面调用

----------
查看完整版(【golang】defer详解)


阅读 3.4k
1 篇内容引用

去去

220 声望
42 粉丝
0 条评论

去去

220 声望
42 粉丝
文章目录
宣传栏