Golang中的异常处理
异常处理
你或许受够了丑陋的 if err != nil {...}
,那么来聊一聊不太常见的defer
,panic
,recover()
Defer
在函数结束之前执行指定的函数。
实现
下面是_defer
结构的源代码。不细说,因为我也不大懂。
1 |
|
defer chain 是一条单向链表(看上面的 link
)。很多文章说defer
这里是一个栈,其实我们在聊同一件事。
graph LR A[goroutine] B[defer func 3] C[defer func 2] D[defer func 1] A --> B --> C --> D
1 |
|
注意
defer
语句中函数的参数是在defer
语句声明时确定的。- 当函数返回后,defer 声明的函数按照后进先出 (Last In First Out) 的顺序执行。
- 如果函数返回的是命名返回值(named return values),
defer
声明的函数调用可以读写这个返回值。(见小练习!foo3()
) os.Exit()
会造成程序立即终止,此时defer
不会被执行。
defer 与 return
Go语言的函数中return
语句在底层并不是原子操作,它分为给返回值赋值和RET两步。而defer
语句执行的时机就在返回值赋值操作后,RET指令执行前。
graph LR A[返回值 = x] B[执行defer] C[ret] A --> B --> C
可容纳 64 位(包括
__m64
类型)的标量返回值是通过 RAX 返回的。 非标量类型(包括浮点类型、双精度类型和向量类型,例如__m128
、__m128i
、__m128d
)以 XMM0 的形式返回。[Microsoft Docs](x64 调用约定 | Microsoft Learn)
小练习!
1 |
|
提示:你可能需要了解命名返回值函数。
defer
的用途
- 释放资源(数据库连接,文件句柄等)
- 释放读写锁
- 视情况修改返回值
- 执行
recover()
Panic
panic()
会立即造成一个运行时错误。如果这个错误没有被捕获,程序会中止。十分显然的,defer语句会在程序中止前运行。
产生运行时错误意味着某个 goroutine
可以直接中止整个程序,所以你应该慎用。
This is only an example but real library functions should avoid
panic
. If the problem can be masked or worked around, it’s always better to let things continue to run rather than taking down the whole program. One possible counterexample is during initialization: if the library truly cannot set itself up, it might be reasonable to panic, so to speak. —— Effective Go
有时你的程序会遇到一些致命错误(比如,你连不上数据库),你可以继续运行,但没什么意义。这时,直接panic()
或许比较合理。
实现
1 |
|
Recover
解决眼下的panic
并让正常控制流夺回控制权,无论panic
是内部的(数组越界之类的)或是你蓄意 panic()
造成的。
正常情况下,调用 recover 函数将返回 nil 且没有任何效果。只有当前 goroutine
为 panicking
状态,调用 recover
函数将捕获 panic
的值并且回到正常控制流。
特别的:
- 对
Goroutine Dead Lock
无效。实际上,所有的Fatal Error
都无效。(runtime
都炸了,谁还能给你recover
呢) - 对子协程的
panic
无效。recover
只作用于当前goroutine
的_panic
链表。 - 对
os.Exit()
无效。
原理
// 这部分好像有问题,待改动
当 panic
被调用后,程序将立刻终止当前函数的执行,并开始自底向上的回溯 goroutine
的栈,运行defer
函数。 若回溯到达 goroutine
栈的顶端,程序就会终止。调用 recover
将停止回溯过程,并返回传入 panic
的实参。由于在回溯时只有defer
函数能够运行,因此 recover
只能在defer
中才有效。
注意
defer
一定要在可能引发panic
的语句之前定义。
goroutine
源代码节选
1 |
|