前言
程序运行过程中,往往都需要申请资源,比如数据库连接,文件句柄等,程序运行资源使用结束后,需要释放资源,很多语言都有类似在某结束时机 统一的去释放,比如析构函数这类的设计,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
结论:
-
多个 defer 的执行顺序为“后进先出/先进后出”;
-
所有函数在执行 RET 返回指令之前,都会先检查是否存在 defer 语句,若存在则先逆序调用 defer 语句进行收尾工作再退出返回;
-
匿名返回值是在 return 执行时被声明,有名返回值则是在函数声明的同时被声明,因此在 defer 语句中只能访问有名返回值,而不能直接访问匿名返回值;
-
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 |
+---------------+
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 的作用域
-
defer 只对当前协程有效(main 可以看作是主协程)挂在G的defer链上;
-
当任意一条(主)协程发生 panic 时,会执行当前协程中 panic 之前已声明的 defer;
-
在发生 panic 的(主)协程中,如果没有一个 defer 调用 recover()进行恢复,则会在执行完最后一个已声明的 defer 后,引发整个进程崩溃;
-
主动调用 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
}
}