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 | |