golang内幕之defer-panic-recover

问题:defer适用在什么场景?

我们先看下下面一段拷贝文件的代码:

func CopyFile_1(src, dst string) (wlen int64, err error) {
	sfd, err := os.Open(src)
	if err != nil {
		return 0, err
	}

	dfd, err := os.Create(dst)
	if err != nil {
		sfd.Close()
		return 0, err
	}

	wlen, err = io.Copy(dfd, sfd)
	if err != nil {
		sfd.Close()
		sfd.Close()
		return 0, err
	}

	sfd.Close()
	sfd.Close()
	return wlen, nil
}

有没有发现,当有错误发生了,函数在返回前总要对已经打开的文件资源进行回收,即Close操作。

使用defer优化的代码如下:

func CopyFile_2(src, dst string) (wlen int64, err error) {
	sfd, err := os.Open(src)
	if err != nil {
		return 0, err
	}
	defer sfd.Close()

	dfd, err := os.Create(dst)
	if err != nil {
		return 0, err
	}
	defer dfd.Close()

	wlen, err = io.Copy(dfd, sfd)
	if err != nil {
		return 0, err
	}

	return wlen, nil
}

相比之下,少了很多Close的操作代码。

总结:这就是defer的适用场景之一,在函数返回前,做一些回收工作。

func DeferEval_1() {
	i := 0
	i++
	defer func() {
		fmt.Println("i=", i)
	}()
}

估计你能代码输出的是多少,没错就是1

=== RUN   TestDeferEval_1
i= 1
--- PASS: TestDeferEval_1 (0.00s)
PASS
"".DeferEval_1 STEXT size=135 args=0x0 locals=0x28
	0x0000 00000 (defer-panic-recover.go:5)	TEXT	"".DeferEval_1(SB), ABIInternal, $40-0
    ...
	0x0024 00036 (defer-panic-recover.go:6)	MOVQ	$0, "".i+24(SP)
	0x002d 00045 (defer-panic-recover.go:7)	MOVQ	$1, "".i+24(SP)
	0x0036 00054 (defer-panic-recover.go:8)	MOVL	$8, (SP)
	0x003d 00061 (defer-panic-recover.go:8)	PCDATA	$2, $1
	0x003d 00061 (defer-panic-recover.go:8)	LEAQ	"".DeferEval_1.func1·f(SB), AX
    ...
	0x0052 00082 (defer-panic-recover.go:8)	CALL	runtime.deferproc(SB)
\   ...
	0x005e 00094 (defer-panic-recover.go:11)	CALL	runtime.deferreturn(SB)
    ...
	0x006e 00110 (defer-panic-recover.go:8)	CALL	runtime.deferreturn(SB)
    ...
	0x007d 00125 (defer-panic-recover.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x0082 00130 (defer-panic-recover.go:5)	JMP	0

从汇编可以看出,由于i只有go function一处引用,因此只需值传递即可,并没有取i的地址,因为go function后并没有对i操作的代码。

func DeferEval_2() {
	i := 0
	defer func() {
		fmt.Println("i=", i)
	}()
	i++
}

换了一下defer func和i++的位置,结果又会如何呢?

=== RUN   TestDeferEval_2
i= 1
--- PASS: TestDeferEval_2 (0.00s)
PASS
"".DeferEval_2 STEXT size=140 args=0x0 locals=0x28
	0x0000 00000 (defer-panic-recover.go:5)	TEXT	"".DeferEval_2(SB), ABIInternal, $40-0
	...
	0x0024 00036 (defer-panic-recover.go:6)	MOVQ	$0, "".i+24(SP)
	...
	0x0034 00052 (defer-panic-recover.go:7)	LEAQ	"".DeferEval_2.func1·f(SB), AX
    ...
	0x0040 00064 (defer-panic-recover.go:7)	LEAQ	"".i+24(SP), AX
    ...
	0x004a 00074 (defer-panic-recover.go:7)	CALL	runtime.deferproc(SB)
    ...
	0x0055 00085 (defer-panic-recover.go:10)	MOVQ	"".i+24(SP), AX
	0x005a 00090 (defer-panic-recover.go:10)	INCQ	AX
	0x005d 00093 (defer-panic-recover.go:10)	MOVQ	AX, "".i+24(SP)
	0x0062 00098 (defer-panic-recover.go:11)	XCHGL	AX, AX
	0x0063 00099 (defer-panic-recover.go:11)	CALL	runtime.deferreturn(SB)
    ...
	0x0073 00115 (defer-panic-recover.go:7)	CALL	runtime.deferreturn(SB)
    ...
	0x0082 00130 (defer-panic-recover.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x0087 00135 (defer-panic-recover.go:5)	JMP	0

汇编看出,取了i的地址作为参数传入(LEAQ    "".i+24(SP), AX)。

func DeferEval_3() {
	i := 0
	defer func(i int) {
		fmt.Println("i=", i)
	}(i)
	i++
}
=== RUN   TestDeferEval_3
i= 0
--- PASS: TestDeferEval_3 (0.00s)
PASS
"".DeferEval_3 STEXT size=139 args=0x0 locals=0x28
	0x0000 00000 (defer-panic-recover.go:5)	TEXT	"".DeferEval_3(SB), ABIInternal, $40-0
    ...
	0x0024 00036 (defer-panic-recover.go:6)	MOVQ	$0, "".i+24(SP)
    ...
	0x0034 00052 (defer-panic-recover.go:7)	LEAQ	"".DeferEval_3.func1·f(SB), AX
    ...
	0x0049 00073 (defer-panic-recover.go:7)	CALL	runtime.deferproc(SB)
    ...
	0x0054 00084 (defer-panic-recover.go:10)	MOVQ	"".i+24(SP), AX
	0x0059 00089 (defer-panic-recover.go:10)	INCQ	AX
	0x005c 00092 (defer-panic-recover.go:10)	MOVQ	AX, "".i+24(SP)
	0x0061 00097 (defer-panic-recover.go:11)	XCHGL	AX, AX
	0x0062 00098 (defer-panic-recover.go:11)	CALL	runtime.deferreturn(SB)
	...
	0x0072 00114 (defer-panic-recover.go:7)	CALL	runtime.deferreturn(SB)
    ...
	0x0081 00129 (defer-panic-recover.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x0086 00134 (defer-panic-recover.go:5)	JMP	0

并没有取i的地址,跟普通股函数传值一致。

总结:defer func总是在调用函数返回前执行,我们可以传参数进func,如TestDeferEval_3,此时传入的值就是0,因此打印出来的接口就是0;而TestDeferEval_1和TestDeferEval_2没给func传如任何参数,直接使用调用函数内声明的i,i在go function还有引用操作,估计传的是地址。

我们写一个稍微复杂点的例子:

func DeferEval_4() {
	//loop 1
	for i := 0; i < 5; i++ {
		defer func() {
			fmt.Println("i=",i)
		}()
	}

	//loop 2
	for j := 0; j < 5; j++ {
		defer func(v int) {
			fmt.Println("j=",v)
		}(j)
	}
}

这里引入两个loop的目的是为了分别给for循环声明的变量i和j创建两个不同的作用域,i属于loop 1,当最后一次i++,即为5时候,不满足条件,此时就会退出for循环,此时也是defer func计算i的值的时刻;j属于loop 2,由于defer func采用了传参,因此j是实时计算的,即j=0时,v=0,j=1时,v=1...。按照我们的结论应该是j会输出0,1,2,3,4,而i会输出5,5,5,5,5。

运行结果:

=== RUN   TestDeferEval_4
j= 4
j= 3
j= 2
j= 1
j= 0
i= 5
i= 5
i= 5
i= 5
i= 5
--- PASS: TestDeferEval_4 (0.00s)
PASS
"".DeferEval_4 STEXT size=292 args=0x0 locals=0x38
	0x0000 00000 (defer-panic-recover.go:5)	TEXT	"".DeferEval_4(SB), ABIInternal, $56-0
    ...
	0x0028 00040 (defer-panic-recover.go:7)	LEAQ	type.int(SB), AX
	0x002f 00047 (defer-panic-recover.go:7)	PCDATA	$2, $0
	0x002f 00047 (defer-panic-recover.go:7)	MOVQ	AX, (SP)
	0x0033 00051 (defer-panic-recover.go:7)	CALL	runtime.newobject(SB)
    ...
	0x0038 00056 (defer-panic-recover.go:7)	MOVQ	8(SP), AX
    ...
	0x003d 00061 (defer-panic-recover.go:7)	MOVQ	AX, "".&i+40(SP)
	0x0042 00066 (defer-panic-recover.go:7)	PCDATA	$2, $0
	0x0042 00066 (defer-panic-recover.go:7)	MOVQ	$0, (AX)
	0x0049 00073 (defer-panic-recover.go:7)	JMP	75
	0x004b 00075 (defer-panic-recover.go:7)	PCDATA	$2, $1
	0x004b 00075 (defer-panic-recover.go:7)	MOVQ	"".&i+40(SP), AX
	0x0050 00080 (defer-panic-recover.go:7)	PCDATA	$2, $0
	0x0050 00080 (defer-panic-recover.go:7)	CMPQ	(AX), $5
	0x0054 00084 (defer-panic-recover.go:7)	JLT	88
	0x0056 00086 (defer-panic-recover.go:7)	JMP	172
	0x0058 00088 (defer-panic-recover.go:8)	PCDATA	$2, $1
	0x0058 00088 (defer-panic-recover.go:8)	MOVQ	"".&i+40(SP), AX
	0x005d 00093 (defer-panic-recover.go:10)	MOVQ	AX, ""..autotmp_5+32(SP)
	0x0062 00098 (defer-panic-recover.go:8)	MOVL	$8, (SP)
	0x0069 00105 (defer-panic-recover.go:8)	PCDATA	$2, $2
	0x0069 00105 (defer-panic-recover.go:8)	LEAQ	"".DeferEval_4.func1·f(SB), CX
    ...
	0x0070 00112 (defer-panic-recover.go:8)	MOVQ	CX, 8(SP)
	0x0075 00117 (defer-panic-recover.go:8)	PCDATA	$2, $0
	0x0075 00117 (defer-panic-recover.go:8)	MOVQ	AX, 16(SP)
	0x007a 00122 (defer-panic-recover.go:8)	CALL	runtime.deferproc(SB)
	0x007f 00127 (defer-panic-recover.go:8)	TESTL	AX, AX
	0x0081 00129 (defer-panic-recover.go:8)	JNE	156
	0x0083 00131 (defer-panic-recover.go:8)	JMP	133
	0x0085 00133 (defer-panic-recover.go:7)	PCDATA	$2, $-2
	0x0085 00133 (defer-panic-recover.go:7)	PCDATA	$0, $-2
	0x0085 00133 (defer-panic-recover.go:7)	JMP	135
	0x0087 00135 (defer-panic-recover.go:7)	PCDATA	$2, $1
	0x0087 00135 (defer-panic-recover.go:7)	PCDATA	$0, $1
	0x0087 00135 (defer-panic-recover.go:7)	MOVQ	"".&i+40(SP), AX
	0x008c 00140 (defer-panic-recover.go:7)	PCDATA	$2, $0
	0x008c 00140 (defer-panic-recover.go:7)	MOVQ	(AX), AX
	0x008f 00143 (defer-panic-recover.go:7)	PCDATA	$2, $3
	0x008f 00143 (defer-panic-recover.go:7)	MOVQ	"".&i+40(SP), CX
	0x0094 00148 (defer-panic-recover.go:7)	INCQ	AX
	0x0097 00151 (defer-panic-recover.go:7)	PCDATA	$2, $0
	0x0097 00151 (defer-panic-recover.go:7)	MOVQ	AX, (CX)
	0x009a 00154 (defer-panic-recover.go:7)	JMP	75
	0x009c 00156 (defer-panic-recover.go:8)	PCDATA	$0, $0
	0x009c 00156 (defer-panic-recover.go:8)	XCHGL	AX, AX
	0x009d 00157 (defer-panic-recover.go:8)	CALL	runtime.deferreturn(SB)
	0x00a2 00162 (defer-panic-recover.go:8)	MOVQ	48(SP), BP
	0x00a7 00167 (defer-panic-recover.go:8)	ADDQ	$56, SP
	0x00ab 00171 (defer-panic-recover.go:8)	RET
	0x00ac 00172 (defer-panic-recover.go:14)	MOVQ	$0, "".j+24(SP)
	0x00b5 00181 (defer-panic-recover.go:14)	JMP	183
	0x00b7 00183 (defer-panic-recover.go:14)	CMPQ	"".j+24(SP), $5
	0x00bd 00189 (defer-panic-recover.go:14)	JLT	193
	0x00bf 00191 (defer-panic-recover.go:14)	JMP	266
	0x00c1 00193 (defer-panic-recover.go:15)	MOVL	$8, (SP)
	0x00c8 00200 (defer-panic-recover.go:15)	PCDATA	$2, $1
	0x00c8 00200 (defer-panic-recover.go:15)	LEAQ	"".DeferEval_4.func2·f(SB), AX
	0x00cf 00207 (defer-panic-recover.go:15)	PCDATA	$2, $0
	0x00cf 00207 (defer-panic-recover.go:15)	MOVQ	AX, 8(SP)
	0x00d4 00212 (defer-panic-recover.go:15)	MOVQ	"".j+24(SP), CX
	0x00d9 00217 (defer-panic-recover.go:15)	MOVQ	CX, 16(SP)
	0x00de 00222 (defer-panic-recover.go:15)	CALL	runtime.deferproc(SB)
	0x00e3 00227 (defer-panic-recover.go:15)	TESTL	AX, AX
	0x00e5 00229 (defer-panic-recover.go:15)	JNE	250
	0x00e7 00231 (defer-panic-recover.go:15)	JMP	233
	0x00e9 00233 (defer-panic-recover.go:14)	PCDATA	$2, $-2
	0x00e9 00233 (defer-panic-recover.go:14)	PCDATA	$0, $-2
	0x00e9 00233 (defer-panic-recover.go:14)	JMP	235
	0x00eb 00235 (defer-panic-recover.go:14)	PCDATA	$2, $0
	0x00eb 00235 (defer-panic-recover.go:14)	PCDATA	$0, $0
	0x00eb 00235 (defer-panic-recover.go:14)	MOVQ	"".j+24(SP), AX
	0x00f0 00240 (defer-panic-recover.go:14)	INCQ	AX
	0x00f3 00243 (defer-panic-recover.go:14)	MOVQ	AX, "".j+24(SP)
	0x00f8 00248 (defer-panic-recover.go:14)	JMP	183
	0x00fa 00250 (defer-panic-recover.go:15)	XCHGL	AX, AX
	0x00fb 00251 (defer-panic-recover.go:15)	CALL	runtime.deferreturn(SB)
	0x0100 00256 (defer-panic-recover.go:15)	MOVQ	48(SP), BP
	0x0105 00261 (defer-panic-recover.go:15)	ADDQ	$56, SP
	0x0109 00265 (defer-panic-recover.go:15)	RET
	0x010a 00266 (defer-panic-recover.go:19)	XCHGL	AX, AX
	0x010b 00267 (defer-panic-recover.go:19)	CALL	runtime.deferreturn(SB)
	0x0110 00272 (defer-panic-recover.go:19)	MOVQ	48(SP), BP
	0x0115 00277 (defer-panic-recover.go:19)	ADDQ	$56, SP
	0x0119 00281 (defer-panic-recover.go:19)	RET
	0x011a 00282 (defer-panic-recover.go:19)	NOP
	0x011a 00282 (defer-panic-recover.go:5)	PCDATA	$0, $-1
	0x011a 00282 (defer-panic-recover.go:5)	PCDATA	$2, $-1
	0x011a 00282 (defer-panic-recover.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x011f 00287 (defer-panic-recover.go:5)	JMP	0

可以看出,i取了地址值传入,j普通传值传入。

总结:如果按照函数传值传入,按传入的值处理,如果在go function内部直接使用调用函数变量,如果go function后仍会有操作调用函数变量的情况,则传入的则是变量的地址值。

i和j输出的结果符合我们的预期,但是输出的顺却有出入,但顺序我们接下来会探讨。

func DeferOrder_1() {
	fmt.Println("0")
	defer func() {
		fmt.Println("4")
	}()
	defer func() {
		fmt.Println("3")
	}()
	defer func() {
		fmt.Println("2")
	}()
	fmt.Println("1")
}

之前说过,defer func是调用函数返回前执行的,那么先会输出0和1。然后defer func采用了先进后出,即会输出2,3,4。

=== RUN   TestDeferOrder_1
0
1
2
3
4
--- PASS: TestDeferOrder_1 (0.00s)
PASS
func DeferRealTime_1() (ret int) {
	defer func() {
		ret++
	}()
	return 99
}
func TestDeferRealTime_1(t *testing.T) {
	ret := DeferRealTime_1()
	fmt.Println(ret)
}
=== RUN   TestDeferRealTime_1
100
--- PASS: TestDeferRealTime_1 (0.00s)
PASS
"".DeferRealTime_1 STEXT size=136 args=0x8 locals=0x20
	0x0000 00000 (defer-real-time.go:3)	TEXT	"".DeferRealTime_1(SB), ABIInternal, $32-8
    ...
	0x0024 00036 (defer-real-time.go:3)	MOVQ	$0, "".ret+40(SP)
    ...
	0x0034 00052 (defer-real-time.go:4)	LEAQ	"".DeferRealTime_1.func1·f(SB), AX
    ...
	0x0040 00064 (defer-real-time.go:4)	LEAQ	"".ret+40(SP), AX
	0x0045 00069 (defer-real-time.go:4)	PCDATA	$2, $0
	0x0045 00069 (defer-real-time.go:4)	MOVQ	AX, 16(SP)
	0x004a 00074 (defer-real-time.go:4)	CALL	runtime.deferproc(SB)
    ...
	0x0055 00085 (defer-real-time.go:7)	MOVQ	$99, "".ret+40(SP)
	0x005e 00094 (defer-real-time.go:7)	XCHGL	AX, AX
	0x005f 00095 (defer-real-time.go:7)	CALL	runtime.deferreturn(SB)
	0x0064 00100 (defer-real-time.go:7)	MOVQ	24(SP), BP
	0x0069 00105 (defer-real-time.go:7)	ADDQ	$32, SP
	0x006d 00109 (defer-real-time.go:7)	RET
	0x006e 00110 (defer-real-time.go:4)	XCHGL	AX, AX
	0x006f 00111 (defer-real-time.go:4)	CALL	runtime.deferreturn(SB)
    ...
	0x007e 00126 (defer-real-time.go:3)	CALL	runtime.morestack_noctxt(SB)
	0x0083 00131 (defer-real-time.go:3)	JMP	0
"".DeferRealTime_1.func1 STEXT nosplit size=20 args=0x8 locals=0x0
	0x0000 00000 (defer-real-time.go:4)	TEXT	"".DeferRealTime_1.func1(SB), NOSPLIT|ABIInternal, $0-8
    ...
	0x0000 00000 (defer-real-time.go:5)	MOVQ	"".&ret+8(SP), AX
    ...
	0x0008 00008 (defer-real-time.go:5)	MOVQ	"".&ret+8(SP), CX
	0x000d 00013 (defer-real-time.go:5)	INCQ	AX
    ...
	0x0013 00019 (defer-real-time.go:6)	RET

取地址传入,然后在go function使用地址值变量进行加1操作。

这才是真是调用函数返回的时机。

 

接下来,探讨一下panic:

func Panic_1() {
	fmt.Println("0")
	panic("rise exception")
	fmt.Println("1")
}
package main

func main() {
	Panic_1()
}
"".Panic_1 STEXT size=178 args=0x0 locals=0x68
	0x0000 00000 (panic-builtin.go:5)	TEXT	"".Panic_1(SB), ABIInternal, $104-0
    ...
	0x0048 00072 (panic-builtin.go:6)	LEAQ	"".statictmp_0(SB), CX
	0x004f 00079 (panic-builtin.go:6)	PCDATA	$2, $1
	0x004f 00079 (panic-builtin.go:6)	MOVQ	CX, ""..autotmp_0+64(SP)
    ..
	0x0058 00088 (panic-builtin.go:6)	MOVQ	AX, ""..autotmp_1+72(SP)
	0x005d 00093 (panic-builtin.go:6)	MOVQ	$1, ""..autotmp_1+80(SP)
	0x0066 00102 (panic-builtin.go:6)	MOVQ	$1, ""..autotmp_1+88(SP)
	0x006f 00111 (panic-builtin.go:6)	PCDATA	$2, $0
	0x006f 00111 (panic-builtin.go:6)	MOVQ	AX, (SP)
	0x0073 00115 (panic-builtin.go:6)	MOVQ	$1, 8(SP)
	0x007c 00124 (panic-builtin.go:6)	MOVQ	$1, 16(SP)
	0x0085 00133 (panic-builtin.go:6)	CALL	fmt.Println(SB)
	0x008a 00138 (panic-builtin.go:7)	PCDATA	$2, $1
	0x008a 00138 (panic-builtin.go:7)	LEAQ	type.string(SB), AX
    ...
	0x0095 00149 (panic-builtin.go:7)	LEAQ	"".statictmp_1(SB), AX
	0x009c 00156 (panic-builtin.go:7)	PCDATA	$2, $0
	0x009c 00156 (panic-builtin.go:7)	MOVQ	AX, 8(SP)
	0x00a1 00161 (panic-builtin.go:7)	CALL	runtime.gopanic(SB)
	0x00a6 00166 (panic-builtin.go:7)	UNDEF
	0x00a8 00168 (panic-builtin.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x00ad 00173 (panic-builtin.go:5)	JMP	0
...
"".statictmp_1 SRODATA size=16
	0x0000 00 00 00 00 00 00 00 00 0e 00 00 00 00 00 00 00  ................
	rel 0+8 t=1 go.string."rise exception"+0

panic是golang内置的一个函数,编译时翻译成gopanic。

panic发生了,还会正常往下执行吗?

0
panic: rise exception

goroutine 1 [running]:
main.Panic_1()
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:117 +0x9d
main.main()
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:122 +0x27

总结:很明显,当panic发生,不会继续往下执行代码。

当panic遇上recover,会发生什么呢?golang规定recover必须在defer中生效。

func Panic_2() {
	fmt.Println("0")
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("catch exception")
		}
	}()
	panic("rise exception")
	fmt.Println("1")
}

func main() {
	Panic_2()
}
0
catch exception

Process finished with exit code 0

如果有多个recover代码会怎样?

func Panic_3() {
	fmt.Println("0")
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("catch exception_3")
		}
	}()
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("catch exception_2")
		}
	}()
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("catch exception_1")
		}
	}()
	panic("rise exception")
	fmt.Println("1")
}
0
catch exception_1

Process finished with exit code 0

总结:离panic最进的recover会捕获到异常,其他recover捕获不到了。

 

如果func1 -> func2 -> func3 -> func4 -> func5,func5中发生panic,但没通过recover进行恢复,会怎么样?

func Panic_4()  {
	Panic_4_1()
}

func Panic_4_1() {
	Panic_4_2()
}

func Panic_4_2() {
	Panic_4_3()
}

func Panic_4_3() {
	Panic_4_4()
}

func Panic_4_4() {
	Panic_4_5()
}

func Panic_4_5() {
	panic("rise in func  Panic_4_5")
}

func main() {
	Panic_4()
}
panic: rise in func  Panic_4_5

goroutine 1 [running]:
main.Panic_4_5(...)
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:178
main.Panic_4_4(...)
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:174
main.Panic_4_3(...)
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:170
main.Panic_4_2(...)
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:166
main.Panic_4_1(...)
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:162
main.Panic_4(...)
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:158
main.main()
	C:/Users/Administrator/go/src/mq30/defer-panic-recover.go:182 +0x46

Process finished with exit code 2

我们可以在 Panic_4  Panic_4_?任意一个函数增加recover捕获异常代码,可以总结出:

当一个函数发生了panic,会检查当前调用函数是否有recover,没有的话,会往上抛,直到有recover,不然相当于一直会到main.main。

func Panic_4()  {
	fmt.Println("Panic_4 start")
	Panic_4_1()
	fmt.Println("Panic_4 end")
}

func Panic_4_1() {
	fmt.Println("Panic_4_1 start")
	Panic_4_2()
	fmt.Println("Panic_4_1 end")
}

func Panic_4_2() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Panic_4_2 recover, err:", err)
		}
	}()
	fmt.Println("Panic_4_2 start")
	Panic_4_3()
	fmt.Println("Panic_4_2 end")
}

func Panic_4_3() {
	fmt.Println("Panic_4_3 start")
	Panic_4_4()
	fmt.Println("Panic_4_3 end")
}

func Panic_4_4() {
	fmt.Println("Panic_4_4 start")
	Panic_4_5()
	fmt.Println("Panic_4_4 end")
}

func Panic_4_5() {
	fmt.Println("Panic_4_5 start")
	panic("rise in func  Panic_4_5")
	fmt.Println("Panic_4_5 end")
}

func main() {
	Panic_4()
}
Panic_4 start
Panic_4_1 start
Panic_4_2 start
Panic_4_3 start
Panic_4_4 start
Panic_4_5 start
Panic_4_2 recover, err: rise in func  Panic_4_5
Panic_4_1 end
Panic_4 end

Process finished with exit code 0

可以看出,在Panic_4_2调用了recover捕获异常,所以就没往上抛了,即Panic_4_1、Panic_4、main又正常的往下执行了。

 

你可能感兴趣的:(golang内幕)