go defer

前言

        程序运行过程中,往往都需要申请资源,比如数据库连接,文件句柄等,程序运行资源使用结束后,需要释放资源,很多语言都有类似在某结束时机 统一的去释放,比如析构函数这类的设计,go的话也提供了类似的设计 即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行, 最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。defer 中还能调用recover函数,当程序在panic时,可以捕获panic进行处理,类似try  catch。

defer执行时机:函数返回时,和panic时 ,程序退出   defer执行顺序:逆序执行

热身

case 1:

func ReadWrite() bool {
    fd,err := file.Open("file")
    defer fd.Close()
    if err != nil {
        return false
    }
    return true
}

存在问题:

fd,err := file.Open("file") ,如果err 不等于 nil, fd=nil ,执行fd.Close()会panic

正确用法:

func ReadWrite() bool {
    fd , err := file.Open("file")
    if err != nil {
        return false
    }
    
    defer fd.Close()
    return true
}

case 2:

匿名返回:

func main()  {
    fmt.Println(a())
}

func a() int {
    var i int
    defer func() {
        i++
        fmt.Println("a defer2:", i) 
    }()
    defer func() {
        i++
        fmt.Println("a defer1:", i) 
    }()

    return i
}

结果输出:

a defer1:1

a defer2:2

0

func main()  {
    fmt.Println(*a())
}

func a() *int {
    var i int
    defer func() {
        i++
        fmt.Println("a defer2:", i) 
    }()
    defer func() {
        i++
        fmt.Println("a defer1:", i) 
    }()

    return &i
}

结果输出:

a defer1:1

2a defer2:,

2

case 3:

有名返回值:

func main()  {
    fmt.Println(b())
}
func b() (i int) {
    defer func() {
        i++
        fmt.Println("b defer2:", i) // 打印结果为 b defer2: 2
    }()
    defer func() {
        i++
        fmt.Println("b defer1:", i) // 打印结果为 b defer1: 1
    }()
    return i // 或者直接 return 效果相同
}

结果输出

b defer1:1,

b defer2:2,

2

结论:

  1. 多个 defer 的执行顺序为“后进先出/先进后出”;

  2. 所有函数在执行 RET 返回指令之前,都会先检查是否存在 defer 语句,若存在则先逆序调用 defer 语句进行收尾工作再退出返回;

  3. 匿名返回值是在 return 执行时被声明,有名返回值则是在函数声明的同时被声明,因此在 defer 语句中只能访问有名返回值,而不能直接访问匿名返回值;

  4. return 其实应该包含前后两个步骤:第一步是给返回值赋值(若为有名返回值则直接赋值,若为匿名返回值则先声明再赋值);第二步是调用 RET 返回指令并传入返回值,而 RET 则会检查 defer 是否存在,若存在就先逆序插播 defer 语句,最后 RET 携带返回值退出函数;

因此,‍‍defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着 defer 开始执行一些收尾工作;最后 RET 指令携带返回值退出函数。

go多返回值实现:

package main

func main() {

}

func A(){
    var a,b int
    a = 1
    b = 2
    B(a,b)
    return
}

func B(e,f int)(int,int){
    return e +1 ,f +1
}


//函数调用栈布局
FP: Frame pointer: arguments and locals.
PC: Program counter: jumps and branches.
SB: Static base pointer: global symbols.
SP: Stack pointer: top of stack.

					   -----------------                                           
                       current func arg0                                           
                       ----------------- <----------- FP(pseudo FP)                
                        caller ret addr                                            
                       +---------------+                                           
                       | caller BP(*)  |                                           
                       ----------------- <----------- SP(pseudo SP,实际上是当前栈帧的 BP 位置)
                       |   Local Var0  |                                           
                       -----------------                                           
                       |   Local Var1  |                                           
                       -----------------                                           
                       |   Local Var2  |                                           
                       -----------------                -                          
                       |   ........    |                                           
                       -----------------                                           
                       |   Local VarN  |                                           
                       -----------------                                           
                       |               |                                           
                       |               |                                           
                       |  temporarily  |                                           
                       |  unused space |                                           
                       |               |                                           
                       |               |                                           
                       -----------------                                           
                       |  call retn    |                                           
                       -----------------                                           
                       |  call ret(n-1)|                                           
                       -----------------                                           
                       |  ..........   |                                           
                       -----------------                                           
                       |  call ret1    |                                           
                       -----------------                                           
                       |  call argn    |                                           
                       -----------------                                           
                       |   .....       |                                           
                       -----------------                                           
                       |  call arg3    |                                           
                       -----------------                                           
                       |  call arg2    |                                           
                       |---------------|                                           
                       |  call arg1    |                                           
                       -----------------   <------------  hardware SP 位置           
                       | return addr   |                                           
                       +---------------+       

参考:【Golang】图解函数调用栈

 go tool compile -S -N -L main.go 

"".A STEXT nosplit size=154 args=0x0 locals=0x50
        0x0000 00000 (main.go:7)        TEXT    "".A(SB), NOSPLIT|ABIInternal, $80-0
        0x0000 00000 (main.go:7)        SUBQ    $80, SP
        0x0004 00004 (main.go:7)        MOVQ    BP, 72(SP)
        0x0009 00009 (main.go:7)        LEAQ    72(SP), BP
        0x000e 00014 (main.go:7)        PCDATA  $0, $-2
        0x000e 00014 (main.go:7)        PCDATA  $1, $-2
        0x000e 00014 (main.go:7)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:7)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:7)        FUNCDATA        $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (main.go:8)        PCDATA  $0, $0
        0x000e 00014 (main.go:8)        PCDATA  $1, $0
        0x000e 00014 (main.go:8)        MOVQ    $0, "".a+40(SP)
        0x0017 00023 (main.go:8)        MOVQ    $0, "".b+32(SP)
        0x0020 00032 (main.go:9)        MOVQ    $1, "".a+40(SP)
        0x0029 00041 (main.go:10)       MOVQ    $2, "".b+32(SP)
        0x0032 00050 (main.go:11)       MOVQ    "".a+40(SP), AX
        0x0037 00055 (main.go:11)       MOVQ    AX, "".e+24(SP)
        0x003c 00060 (main.go:11)       MOVQ    "".b+32(SP), AX
        0x0041 00065 (main.go:11)       MOVQ    AX, "".f+16(SP)
        0x0046 00070 (main.go:11)       MOVQ    $0, "".~r2+8(SP)
        0x004f 00079 (main.go:11)       MOVQ    $0, "".~r3(SP)
        0x0057 00087 (<unknown line number>)    NOP
        0x0057 00087 (main.go:16)       MOVQ    "".e+24(SP), AX
        0x005c 00092 (main.go:16)       INCQ    AX
        0x005f 00095 (main.go:11)       MOVQ    AX, ""..autotmp_6+64(SP)
        0x0064 00100 (main.go:16)       MOVQ    "".f+16(SP), AX
        0x0069 00105 (main.go:16)       INCQ    AX
        0x006c 00108 (main.go:11)       MOVQ    AX, ""..autotmp_7+56(SP)
        0x0071 00113 (main.go:11)       MOVQ    ""..autotmp_6+64(SP), AX
        0x0076 00118 (main.go:11)       MOVQ    AX, "".~r2+8(SP)
        0x007b 00123 (main.go:11)       MOVQ    ""..autotmp_7+56(SP), AX
        0x0080 00128 (main.go:11)       MOVQ    AX, "".~r3(SP)
        0x0084 00132 (main.go:11)       JMP     134
        0x0086 00134 (main.go:11)       MOVQ    "".~r2+8(SP), AX
        0x008b 00139 (main.go:11)       MOVQ    AX, ""..autotmp_8+48(SP)
        0x0090 00144 (main.go:12)       MOVQ    72(SP), BP
        0x0095 00149 (main.go:12)       ADDQ    $80, SP
        0x0099 00153 (main.go:12)       RET


"".B STEXT nosplit size=45 args=0x20 locals=0x0
        0x0000 00000 (main.go:15)       TEXT    "".B(SB), NOSPLIT|ABIInternal, $0-32
        0x0000 00000 (main.go:15)       PCDATA  $0, $-2
        0x0000 00000 (main.go:15)       PCDATA  $1, $-2
        0x0000 00000 (main.go:15)       FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:15)       FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:15)       FUNCDATA        $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:15)       PCDATA  $0, $0
        0x0000 00000 (main.go:15)       PCDATA  $1, $0
        0x0000 00000 (main.go:15)       MOVQ    $0, "".~r2+24(SP)
        0x0009 00009 (main.go:15)       MOVQ    $0, "".~r3+32(SP)
        0x0012 00018 (main.go:16)       MOVQ    "".e+8(SP), AX
        0x0017 00023 (main.go:16)       INCQ    AX
        0x001a 00026 (main.go:16)       MOVQ    AX, "".~r2+24(SP)
        0x001f 00031 (main.go:16)       MOVQ    "".f+16(SP), AX
        0x0024 00036 (main.go:16)       INCQ    AX
        0x0027 00039 (main.go:16)       MOVQ    AX, "".~r3+32(SP)
        0x002c 00044 (main.go:16)       RET


80-0

A()
retaddr       80         BP  = 72
        bp    72  *BP
              64         e +1 = 2
              56         f+1 = 3
              48         _
a=1           40
b=2           32
e=1           24    call1
f=2           16    call2
r2            8     (ret1)    e+1              32
r3            0     (ret2)    f+1   sp         24
retAddr                                        16
     *BP                                        8
                                                B(sp+8)


B()
retaddr 
               32   f+1        
               24   e+1
         f     16
         e     8
               0   SP

 golang的返回参数是放在栈里面的,并不像c语言放在寄存器eax中. 所以golang能够支持多个参数返回

逃逸分析:

func main()  {
    _ = a()

}

func a() *int {
    var i int

     bb := func() {
        i++
        //fmt.Println("a defer1:", i)
    }

    defer bb()
    return &i

}

//逃逸分析  go build -gcflags '-m -l' main.go
MacBook-Pro:test mac$ go build -gcflags '-m -l' main.go
# command-line-arguments
./main.go:9:9: moved to heap: i
./main.go:11:12: func literal does not escape



//闭包结构
  rel 32+8 t=1 runtime.gcbits.02+0
  rel 40+4 t=5 type..namedata.*struct { F uintptr; i *int }-+0
  rel 44+4 t=6 type.*struct { F uintptr; "".i *int }+0


执行流程:返回值赋值,执行defer函数 ,  ret

go :1.14

package main

import "fmt"

func main()  {

    myFunc()

}

func myFunc() (int, int) {

    a := 1
    b := 2
    for i:= 1; i<= 1;i ++ {
        defer fmt.Println(a,b)
    }
    return a,b
}
"".myFunc STEXT size=288 args=0x10 locals=0x50
        0x0000 00000 (main.go:9)        TEXT    "".myFunc(SB), ABIInternal, $80-16
        0x0000 00000 (main.go:9)        MOVQ    (TLS), CX
        0x0009 00009 (main.go:9)        CMPQ    SP, 16(CX)
        0x000d 00013 (main.go:9)        PCDATA  $0, $-2
        0x000d 00013 (main.go:9)        JLS     278
        0x0013 00019 (main.go:9)        PCDATA  $0, $-1
        0x0013 00019 (main.go:9)        SUBQ    $80, SP
        0x0017 00023 (main.go:9)        MOVQ    BP, 72(SP)
        0x001c 00028 (main.go:9)        LEAQ    72(SP), BP
        0x0021 00033 (main.go:9)        PCDATA  $0, $-2
        0x0021 00033 (main.go:9)        PCDATA  $1, $-2
        0x0021 00033 (main.go:9)        FUNCDATA        $0, gclocals·3e27b3aa6b89137cce48b3379a2a6610(SB)
        0x0021 00033 (main.go:9)        FUNCDATA        $1, gclocals·e8422658954f123ea4f8723e9d4cf051(SB)
        0x0021 00033 (main.go:9)        FUNCDATA        $2, gclocals·344e3dc7d8688f9640b5b4f90ec73423(SB)
        0x0021 00033 (main.go:9)        PCDATA  $0, $0
        0x0021 00033 (main.go:9)        PCDATA  $1, $0
        0x0021 00033 (main.go:9)        MOVQ    $0, "".~r0+88(SP)
        0x002a 00042 (main.go:9)        MOVQ    $0, "".~r1+96(SP)
        0x0033 00051 (main.go:11)       PCDATA  $0, $1
        0x0033 00051 (main.go:11)       LEAQ    type.int(SB), AX
        0x003a 00058 (main.go:11)       PCDATA  $0, $0
        0x003a 00058 (main.go:11)       MOVQ    AX, (SP)
        0x003e 00062 (main.go:11)       CALL    runtime.newobject(SB)
        0x0043 00067 (main.go:11)       PCDATA  $0, $1
        0x0043 00067 (main.go:11)       MOVQ    8(SP), AX
        0x0048 00072 (main.go:11)       PCDATA  $1, $1
        0x0048 00072 (main.go:11)       MOVQ    AX, "".&a+64(SP)
        0x004d 00077 (main.go:11)       PCDATA  $0, $0
        0x004d 00077 (main.go:11)       MOVQ    $1, (AX)
        0x0054 00084 (main.go:12)       PCDATA  $0, $1
        0x0054 00084 (main.go:12)       LEAQ    type.int(SB), AX
        0x005b 00091 (main.go:12)       PCDATA  $0, $0
        0x005b 00091 (main.go:12)       MOVQ    AX, (SP)
        0x005f 00095 (main.go:12)       CALL    runtime.newobject(SB)
        0x0064 00100 (main.go:12)       PCDATA  $0, $1
        0x0064 00100 (main.go:12)       MOVQ    8(SP), AX
        0x0069 00105 (main.go:12)       PCDATA  $1, $2
        0x0069 00105 (main.go:12)       MOVQ    AX, "".&b+56(SP)
        0x006e 00110 (main.go:12)       PCDATA  $0, $0
        0x006e 00110 (main.go:12)       MOVQ    $2, (AX)
        0x0075 00117 (main.go:13)       MOVQ    $1, "".i+32(SP)
        0x007e 00126 (main.go:13)       JMP     128
        0x0080 00128 (main.go:13)       CMPQ    "".i+32(SP), $1
        0x0086 00134 (main.go:13)       JLE     138
        0x0088 00136 (main.go:13)       JMP     236
        0x008a 00138 (main.go:14)       PCDATA  $0, $1
        0x008a 00138 (main.go:14)       MOVQ    "".&a+64(SP), AX
        0x008f 00143 (main.go:17)       PCDATA  $0, $0
        0x008f 00143 (main.go:17)       PCDATA  $1, $3
        0x008f 00143 (main.go:17)       MOVQ    AX, ""..autotmp_8+48(SP)
        0x0094 00148 (main.go:14)       PCDATA  $0, $1
        0x0094 00148 (main.go:14)       MOVQ    "".&b+56(SP), AX
        0x0099 00153 (main.go:17)       MOVQ    AX, ""..autotmp_9+40(SP)
        0x009e 00158 (main.go:14)       MOVL    $16, (SP)
        0x00a5 00165 (main.go:14)       PCDATA  $0, $2
        0x00a5 00165 (main.go:14)       LEAQ    "".myFunc.func1·f(SB), CX
        0x00ac 00172 (main.go:14)       PCDATA  $0, $1
        0x00ac 00172 (main.go:14)       MOVQ    CX, 8(SP)
        0x00b1 00177 (main.go:14)       PCDATA  $0, $3
        0x00b1 00177 (main.go:14)       PCDATA  $1, $2
        0x00b1 00177 (main.go:14)       MOVQ    ""..autotmp_8+48(SP), DX
        0x00b6 00182 (main.go:14)       PCDATA  $0, $1
        0x00b6 00182 (main.go:14)       MOVQ    DX, 16(SP)
        0x00bb 00187 (main.go:14)       PCDATA  $0, $0
        0x00bb 00187 (main.go:14)       MOVQ    AX, 24(SP)
        0x00c0 00192 (main.go:14)       CALL    runtime.deferproc(SB) //注册defer
        0x00c5 00197 (main.go:14)       TESTL   AX, AX
        0x00c7 00199 (main.go:14)       JNE     220
        0x00c9 00201 (main.go:14)       JMP     203
        0x00cb 00203 (main.go:13)       PCDATA  $0, $-1
        0x00cb 00203 (main.go:13)       PCDATA  $1, $-1
        0x00cb 00203 (main.go:13)       JMP     205
        0x00cd 00205 (main.go:13)       PCDATA  $0, $0
        0x00cd 00205 (main.go:13)       PCDATA  $1, $2
        0x00cd 00205 (main.go:13)       MOVQ    "".i+32(SP), AX
        0x00d2 00210 (main.go:13)       INCQ    AX
        0x00d5 00213 (main.go:13)       MOVQ    AX, "".i+32(SP)
        0x00da 00218 (main.go:13)       JMP     128
        0x00dc 00220 (main.go:14)       PCDATA  $1, $0
        0x00dc 00220 (main.go:14)       XCHGL   AX, AX
        0x00dd 00221 (main.go:14)       CALL    runtime.deferreturn(SB) //defer return
        0x00e2 00226 (main.go:14)       MOVQ    72(SP), BP
        0x00e7 00231 (main.go:14)       ADDQ    $80, SP
        0x00eb 00235 (main.go:14)       RET
        0x00ec 00236 (main.go:19)       PCDATA  $0, $1
        0x00ec 00236 (main.go:19)       PCDATA  $1, $4
        0x00ec 00236 (main.go:19)       MOVQ    "".&a+64(SP), AX
        0x00f1 00241 (main.go:19)       PCDATA  $0, $0
        0x00f1 00241 (main.go:19)       MOVQ    (AX), AX
        0x00f4 00244 (main.go:19)       MOVQ    AX, "".~r0+88(SP)
        0x00f9 00249 (main.go:19)       PCDATA  $0, $1
        0x00f9 00249 (main.go:19)       PCDATA  $1, $0
        0x00f9 00249 (main.go:19)       MOVQ    "".&b+56(SP), AX
        0x00fe 00254 (main.go:19)       PCDATA  $0, $0
        0x00fe 00254 (main.go:19)       MOVQ    (AX), AX
        0x0101 00257 (main.go:19)       MOVQ    AX, "".~r1+96(SP)
        0x0106 00262 (main.go:19)       XCHGL   AX, AX
        0x0107 00263 (main.go:19)       CALL    runtime.deferreturn(SB)
        0x010c 00268 (main.go:19)       MOVQ    72(SP), BP
        0x0111 00273 (main.go:19)       ADDQ    $80, SP
        0x0115 00277 (main.go:19)       RET
        0x0116 00278 (main.go:19)       NOP
        0x0116 00278 (main.go:9)        PCDATA  $1, $-1
        0x0116 00278 (main.go:9)        PCDATA  $0, $-2
        0x0116 00278 (main.go:9)        CALL    runtime.morestack_noctxt(SB)
        0x011b 00283 (main.go:9)        PCDATA  $0, $-1
        0x011b 00283 (main.go:9)        JMP     0

执行顺序:

1.runtime.deferproc(SB)

2.runtime.deferReturn(SB) 

3.ret

runtime.deferproc(SB)

// Create a new deferred function fn with siz bytes of arguments.
// The compiler turns a defer statement into a call to this.
//go:nosplit
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
	if getg().m.curg != getg() {
		// go code on the system stack can't defer
		throw("defer on system stack")
	}

	// the arguments of fn are in a perilous state. The stack map
	// for deferproc does not describe them. So we can't let garbage
	// collection or stack copying trigger until we've copied them out
	// to somewhere safe. The memmove below does that.
	// Until the copy completes, we can only call nosplit routines.
	sp := getcallersp() //获取sp
	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) //入参的地址
	callerpc := getcallerpc() //程序pc计数

	d := newdefer(siz)
	if d._panic != nil {
		throw("deferproc: d.panic != nil after newdefer")
	}
	d.fn = fn //设置执行的函数
	d.pc = callerpc //设置pc
	d.sp = 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))
	}

	// deferproc returns 0 normally.
	// a deferred func that stops a panic
	// makes the deferproc return 1.
	// the code the compiler generates always
	// checks the return value and jumps to the
	// end of the function if deferproc returns != 0.
	return0()
	// No code can go here - the C return register has
	// been set and must not be clobbered.
}

//go:nosplit
func newdefer(siz int32) *_defer {
	var d *_defer
	sc := deferclass(uintptr(siz))
	gp := getg() //当前G
	if sc < uintptr(len(p{}.deferpool)) {//local p中的缓存deferpool
		pp := gp.m.p.ptr() //p的地址
		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.heap = true  // 堆生成
	d.link = gp._defer  //头插法
	gp._defer = d    
	return d
}

type _defer struct {
	siz     int32 // includes both arguments and results
	started bool
	heap    bool //是否分配在堆上,开放编码会分配在栈,panic会扫描栈
	// openDefer indicates that this _defer is for a frame with open-coded
	// defers. We have only one defer record for the entire frame (which may
	// currently have 0, 1, or more defers active).
	openDefer bool //是否是开放编码
	sp        uintptr  // sp at time of defer
	pc        uintptr  // pc at time of defer
	fn        *funcval // can be nil for open-coded defers
	_panic    *_panic  // panic that is running defer
	link      *_defer  // 指针

	// If openDefer is true, the fields below record values about the stack
	// frame and associated function that has the open-coded defer(s). sp
	// above will be the sp for the frame, and pc will be address of the
	// deferreturn call in the function.
	fd   unsafe.Pointer // funcdata for the function associated with the frame
	varp uintptr        // value of varp for the stack frame
	// framepc is the current pc associated with the stack frame. Together,
	// with sp above (which is the sp associated with the stack frame),
	// framepc/sp can be used as pc/sp pair to continue a stack trace via
	// gentraceback().
	framepc uintptr
}


func deferprocStack(d *_defer) {
	gp := getg()
	if gp.m.curg != gp {
		// go code on the system stack can't defer
		throw("defer on system stack")
	}
	// siz and fn are already set.
	// The other fields are junk on entry to deferprocStack and
	// are initialized here.
	d.started = false
	d.heap = false
	d.openDefer = false
	d.sp = getcallersp()
	d.pc = getcallerpc()
	d.framepc = 0
	d.varp = 0
	// The lines below implement:
	//   d.panic = nil
	//   d.fd = nil
	//   d.link = gp._defer
	//   gp._defer = d
	// But without write barriers. The first three are writes to
	// the stack so they don't need a write barrier, and furthermore
	// are to uninitialized memory, so they must not use a write barrier.
	// The fourth write does not require a write barrier because we
	// explicitly mark all the defer structures, so we don't need to
	// keep track of pointers to them with a write barrier.
	*(*uintptr)(unsafe.Pointer(&d._panic)) = 0
	*(*uintptr)(unsafe.Pointer(&d.fd)) = 0
	*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
	*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))

	return0()
	// No code can go here - the C return register has
	// been set and must not be clobbered.
}

1.去p的DeferPool里面拿,拿不到创建一个,设置sp,pc,fn

2.由于是for 编译器运行时确认不了,走的堆分配(deferprocStack走栈),并设置执行函数等参数

3.头插到G的_derfer链表中

runtime.deferreturn

func deferreturn(arg0 uintptr) {
	gp := getg()
	d := gp._defer
	if d == nil {
		return
	}
	sp := getcallersp()
	if d.sp != sp { //函数的sp
		return
	}
	if d.openDefer { //开发编码
		done := runOpenDeferFrame(gp, d)
		if !done {
			throw("unfinished open-coded defers in deferreturn")
		}
		gp._defer = d.link
		freedefer(d)
		return
	}

	// Moving arguments around.
	//
	// Everything called after this point must be recursively
	// nosplit because the garbage collector won't know the form
	// of the arguments until the jmpdefer can flip the PC over to
	// fn.
	switch d.siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
	default:
		memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
	}
	fn := d.fn
	d.fn = nil
	gp._defer = d.link
	freedefer(d)
	// If the defer function pointer is nil, force the seg fault to happen
	// here rather than in jmpdefer. gentraceback() throws an error if it is
	// called with a callback on an LR architecture and jmpdefer is on the
	// stack, because the stack trace can be incorrect in that case - see
	// issue #8153).
	_ = fn.fn
	jmpdefer(fn, uintptr(unsafe.Pointer(&arg0))) //执行函数
}

1.对比sp,是否是执行的函数

2.pop出derfer, jmpdefer过去执行

defer 的作用域

  1. defer 只对当前协程有效(main 可以看作是主协程)挂在G的defer链上;

  2. 当任意一条(主)协程发生 panic 时,会执行当前协程中 panic 之前已声明的 defer;

  3. 在发生 panic 的(主)协程中,如果没有一个 defer 调用 recover()进行恢复,则会在执行完最后一个已声明的 defer 后,引发整个进程崩溃;

  4. 主动调用 os.Exit(int) 退出进程时,defer 将不再被执行。

// Goexit terminates the goroutine that calls it. No other goroutine is affected.
// Goexit runs all deferred calls before terminating the goroutine. Because Goexit
// is not a panic, any recover calls in those deferred functions will return nil.
//
// Calling Goexit from the main goroutine terminates that goroutine
// without func main returning. Since func main has not returned,
// the program continues execution of other goroutines.
// If all other goroutines exit, the program crashes.
func Goexit() {
	// Run all deferred functions for the current goroutine.
	// This code is similar to gopanic, see that implementation
	// for detailed comments.
	gp := getg()

	// Create a panic object for Goexit, so we can recognize when it might be
	// bypassed by a recover().
	var p _panic
	p.goexit = true
	p.link = gp._panic
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

	addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
	for {
		d := gp._defer
		if d == nil {
			break
		}
		if d.started {
			if d._panic != nil {
				d._panic.aborted = true
				d._panic = nil
			}
			if !d.openDefer {
				d.fn = nil
				gp._defer = d.link
				freedefer(d)
				continue
			}
		}
		d.started = true
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
		if d.openDefer {
			done := runOpenDeferFrame(gp, d)
			if !done {
				// We should always run all defers in the frame,
				// since there is no panic associated with this
				// defer that can be recovered.
				throw("unfinished open-coded defers in Goexit")
			}
			if p.aborted {
				// Since our current defer caused a panic and may
				// have been already freed, just restart scanning
				// for open-coded defers from this frame again.
				addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))
			} else {
				addOneOpenDeferFrame(gp, 0, nil)
			}
		} else {

			// Save the pc/sp in reflectcallSave(), so we can "recover" back to this
			// loop if necessary.
			reflectcallSave(&p, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz))
		}
		if p.aborted {
			// We had a recursive panic in the defer d we started, and
			// then did a recover in a defer that was further down the
			// defer chain than d. In the case of an outstanding Goexit,
			// we force the recover to return back to this loop. d will
			// have already been freed if completed, so just continue
			// immediately to the next defer on the chain.
			p.aborted = false
			continue
		}
		if gp._defer != d {
			throw("bad defer entry in Goexit")
		}
		d._panic = nil
		d.fn = nil
		gp._defer = d.link
		freedefer(d)
		// Note: we ignore recovers here because Goexit isn't a panic
	}
	goexit1()
}
panic , defer, recover
func main()  {

     for i:= 1; i<= 2;i ++ {
         defer myFunc()
     }
     panic("11")
}

func myFunc() (int, int) {
    return 1,7
}


 0x0046 00070 (main.go:6)        PCDATA  $0, $0
        0x0046 00070 (main.go:6)        MOVQ    AX, 8(SP)
        0x004b 00075 (main.go:6)        CALL    runtime.deferproc(SB)
        0x0050 00080 (main.go:6)        TESTL   AX, AX
        0x0052 00082 (main.go:6)        JNE     86
        0x0054 00084 (main.go:6)        JMP     36
        0x0056 00086 (main.go:6)        XCHGL   AX, AX
        0x0057 00087 (main.go:6)        CALL    runtime.deferreturn(SB)
        0x005c 00092 (main.go:6)        MOVQ    40(SP), BP
        0x0061 00097 (main.go:6)        ADDQ    $48, SP
        0x0065 00101 (main.go:6)        RET
        0x0066 00102 (main.go:8)        PCDATA  $0, $1
        0x0066 00102 (main.go:8)        LEAQ    type.string(SB), AX
        0x006d 00109 (main.go:8)        PCDATA  $0, $0
        0x006d 00109 (main.go:8)        MOVQ    AX, (SP)
        0x0071 00113 (main.go:8)        PCDATA  $0, $1
        0x0071 00113 (main.go:8)        LEAQ    ""..stmp_0(SB), AX
        0x0078 00120 (main.go:8)        PCDATA  $0, $0
        0x0078 00120 (main.go:8)        MOVQ    AX, 8(SP)
        0x007d 00125 (main.go:8)        CALL    runtime.gopanic(SB)
        0x0082 00130 (main.go:8)        XCHGL   AX, AX
        0x0083 00131 (main.go:8)        NOP
        0x0083 00131 (main.go:3)        PCDATA  $1, $-1
        0x0083 00131 (main.go:3)        PCDATA  $0, $-2
        0x0083 00131 (main.go:3)        CALL    runtime.morestack_noctxt(SB)
        0x0088 00136 (main.go:3)        PCDATA  $0, $-1
        0x0088 00136 (main.go:3)        JMP     0
// The implementation of the predeclared function panic.
func gopanic(e interface{}) {
	gp := getg()
	if gp.m.curg != gp {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic on system stack")
	}

	if gp.m.mallocing != 0 {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic during malloc")
	}
	if gp.m.preemptoff != "" {
		print("panic: ")
		printany(e)
		print("\n")
		print("preempt off reason: ")
		print(gp.m.preemptoff)
		print("\n")
		throw("panic during preemptoff")
	}
	if gp.m.locks != 0 {
		print("panic: ")
		printany(e)
		print("\n")
		throw("panic holding locks")
	}

	var p _panic
	p.arg = e
	p.link = gp._panic
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

	atomic.Xadd(&runningPanicDefers, 1)

	// By calculating getcallerpc/getcallersp here, we avoid scanning the
	// gopanic frame (stack scanning is slow...)
	addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))

	for {
		d := gp._defer
		if d == nil {
			break
		}

		// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),
		// take defer off list. An earlier panic will not continue running, but we will make sure below that an
		// earlier Goexit does continue running.
		if d.started { //已经开始执行
			if d._panic != nil {//设置触发的panic失效
				d._panic.aborted = true
			}
			d._panic = nil
			if !d.openDefer {
				// For open-coded defers, we need to process the
				// defer again, in case there are any other defers
				// to call in the frame (not including the defer
				// call that caused the panic).
				d.fn = nil
				gp._defer = d.link
				freedefer(d)
				continue
			}
		}

		// Mark defer as started, but keep on list, so that traceback
		// can find and update the defer's argument frame if stack growth
		// or a garbage collection happens before reflectcall starts executing d.fn.
		d.started = true

		// Record the panic that is running the defer.
		// If there is a new panic during the deferred call, that panic
		// will find d in the list and will mark d._panic (this panic) aborted.
		d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))

		done := true
		if d.openDefer {
			done = runOpenDeferFrame(gp, d)
			if done && !d._panic.recovered {
				addOneOpenDeferFrame(gp, 0, nil)
			}
		} else {
			p.argp = unsafe.Pointer(getargp(0))
			reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz)) //执行defer函数
		}
		p.argp = nil

		// reflectcall did not panic. Remove d.
		if gp._defer != d {
			throw("bad defer entry in panic")
		}
		d._panic = nil

		// trigger shrinkage to test stack copy. See stack_test.go:TestStackPanic
		//GC()

		pc := d.pc
		sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
		if done {//释放defer结构
			d.fn = nil
			gp._defer = d.link
			freedefer(d)
		}
		if p.recovered {
			gp._panic = p.link
			if gp._panic != nil && gp._panic.goexit && gp._panic.aborted {
				// A normal recover would bypass/abort the Goexit.  Instead,
				// we return to the processing loop of the Goexit.
				gp.sigcode0 = uintptr(gp._panic.sp)
				gp.sigcode1 = uintptr(gp._panic.pc)
				mcall(recovery)//执行recovery
				throw("bypassed recovery failed") // mcall should not return
			}
			atomic.Xadd(&runningPanicDefers, -1)

			if done {
				// Remove any remaining non-started, open-coded
				// defer entries after a recover, since the
				// corresponding defers will be executed normally
				// (inline). Any such entry will become stale once
				// we run the corresponding defers inline and exit
				// the associated stack frame.
				d := gp._defer
				var prev *_defer
				for d != nil {
					if d.openDefer {
						if d.started {
							// This defer is started but we
							// are in the middle of a
							// defer-panic-recover inside of
							// it, so don't remove it or any
							// further defer entries
							break
						}
						if prev == nil {
							gp._defer = d.link
						} else {
							prev.link = d.link
						}
						newd := d.link
						freedefer(d)
						d = newd
					} else {
						prev = d
						d = d.link
					}
				}
			}

			gp._panic = p.link
			// Aborted panics are marked but remain on the g.panic list.
			// Remove them from the list.
			for gp._panic != nil && gp._panic.aborted {
				gp._panic = gp._panic.link
			}
			if gp._panic == nil { // must be done with signal
				gp.sig = 0
			}
			// Pass information about recovering frame to recovery.
			gp.sigcode0 = uintptr(sp) //设置sp
			gp.sigcode1 = pc //pc
			mcall(recovery)//执行recovery
			throw("recovery failed") // mcall should not return
		}
	}

	// ran out of deferred calls - old-school panic now
	// Because it is unsafe to call arbitrary user code after freezing
	// the world, we call preprintpanics to invoke all necessary Error
	// and String methods to prepare the panic strings before startpanic.
	preprintpanics(gp._panic)

	fatalpanic(gp._panic) // should not return
	*(*int)(nil) = 0      // not reached
}


func recovery(gp *g) {
	// Info about defer passed in G struct.
	sp := gp.sigcode0
	pc := gp.sigcode1

	// d's arguments need to be in the stack.
	if sp != 0 && (sp < gp.stack.lo || gp.stack.hi < sp) {
		print("recover: ", hex(sp), " not in [", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
		throw("bad recovery")
	}

	// Make the deferproc for this d return again,
	// this time returning 1. The calling function will
	// jump to the standard return epilogue.
	gp.sched.sp = sp
	gp.sched.pc = pc
	gp.sched.lr = 0
	gp.sched.ret = 1
	gogo(&gp.sched) //调度
}






//recover()函数
func gorecover(argp uintptr) interface{} {
	// Must be in a function running as part of a deferred call during the panic.
	// Must be called from the topmost function of the call
	// (the function used in the defer statement).
	// p.argp is the argument pointer of that topmost deferred function call.
	// Compare against argp reported by caller.
	// If they match, the caller is the one who can recover.
	gp := getg()
	p := gp._panic
	if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	}
	return nil
}



panic 后会执行已经插入的defer, 遇到recover ,走recover流程,继续执行没有执行的defer

package main

import "fmt"

func main()  {

     defer func() {
         println("====1")
     }()

    defer func() {
        println("====2")
        if err := recover(); err != nil {
            fmt.Printf("p:%+v====",err)
            fmt.Println()
        }
    }()
     panic("11")

     defer func() {
         fmt.Printf("====3333")
     }()
}




MacBook-Pro:test mac$ go run main.go 
====2
p:11====
====1




func main()  {

    defer func() {
        println("====1")
        if err := recover(); err != nil { //panic已经被recover了
            fmt.Printf("p:%+v1111====",err)
            fmt.Println()
        }
    }()

    defer func() {
        println("====2")
        if err := recover(); err != nil {
            fmt.Printf("p:%+v====",err)
            fmt.Println()
        }
    }()
    panic("11")

    defer func() {
        fmt.Printf("====3333")
    }()
}

MacBook-Pro:test mac$ go run main.go 
====2
p:11====
====1





package main

import "fmt"
func main() {

    defer func() {
        fmt.Println()
        println("====1")
        if err := recover(); err != nil { //panic已经被recover了
            fmt.Printf("p:%+v1111====", err)
            fmt.Println()
        }
    }()

    defer func() {
        println("====2")
        if err := recover(); err != nil {
            fmt.Printf("p:%+v====", err)
            panic("2222")
            fmt.Println()
        }
    }()
    panic("11")

    defer func() {
        fmt.Printf("====3333")
    }()

}


MacBook-Pro:test mac$ go run main.go 
====2
p:11====
====1
p:22221111====
MacBook-Pro:test mac$ 


工作中遇到的一起问题:

数据库开启事务,程序panic没提交,也没回滚导致链接用完,大致代码流程


func main()  {

    db := helpers.MysqlClientDemo.begin
    
    ret, err := doSomeThing()
    if err != nil {
        db.RollBack
    }
    
    db.commit()
}

然而doSomeThing()里面有的逻辑分支panic了

正确:


func main()  {

        db := helpers.MysqlClientDemo.begin
        if err:= db.Err();err != nil {
            return err
        }
        defer func() {
            if db != nil {
                db.RollBack()
            }
        }()
        ret, err := doSomeThing()
        if err != nil {
            return err
        }

        if err := db.commit().Err();err != nil {
            return err
        }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值