一、宕机(Panic)—— 程序终止运行
我们知道GO语言是编译型语言,在代码编译阶段会捕获很多错误,但是有些错误只能在运行时检查,如数组访问越界、空指针引用等,这些运行时错误会引起宕机,也就是我们经常提到的Panic。
通过学习我们在 函数function 一节我们总结的内容,我们知道:一般而言,当宕机发生时,程序会中断运行,并立即执行在该 协程goroutine (第6章我们将讲到,可以先理解成线程)中被延迟的函数(defer 机制),随后,程序崩溃并输出日志信息,日志信息包括 panic value 和函数调用的堆栈跟踪信息,panic value 通常是某种错误信息。
因为panic在GO语言中属于严重错误,会导致程序终止运行,所以我们需要消除一切可能的panic。
panic除了出现故障时被触发外,我们还可以手动触发宕机,让程序崩溃,这样开发者可以及时地发现错误,同时减少可能的损失。
func main() {
//内建函数panic,参数可以是任意类型的
panic("crash")
}
//输出
panic: crash
goroutine 1 [running]:
main.main()
D:/code/main.go:4 +0x40
exit status 2
二、延迟执行(Defer)—— 做一些“补救”措施
具体内容可以参考 延迟调用 这一节的内容。
三、宕机恢复(Recover)—— 防止程序崩溃
Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
在其他语言里,宕机往往以异常的形式存在,底层抛出异常,上层逻辑通过 try/catch 机制捕获异常,没有被捕获的严重异常会导致宕机,捕获的异常可以被忽略,让代码继续运行。
Go语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover 的宕机恢复机制就对应其他语言中的 try/catch 机制。
下面的代码实现了 ProtectRun() 函数,该函数传入一个匿名函数或闭包后的执行函数,当传入函数以任何形式发生 panic 崩溃后,可以将崩溃发生的错误打印出来,同时允许后面的代码继续运行,不会造成整个进程的崩溃。
package main
import (
"fmt"
"runtime"
)
// 崩溃时需要传递的上下文信息
type panicContext struct {
function string // 所在函数
}
// 保护方式允许一个函数
func ProtectRun(entry func()) {
// 延迟处理的函数
defer func() {
// 发生宕机时,获取panic传递的上下文并打印
err := recover()
switch err.(type) {
case runtime.Error: // 运行时错误
fmt.Println("runtime error:", err)
default: // 非运行时错误
fmt.Println("error:", err)
}
}()
entry()
}
func main() {
fmt.Println("运行前")
// 允许一段手动触发的错误
ProtectRun(func() {
fmt.Println("手动宕机前")
// 使用panic传递上下文
panic(&panicContext{
"手动触发panic",
})
fmt.Println("手动宕机后")
})
// 故意造成空指针访问错误
ProtectRun(func() {
fmt.Println("赋值宕机前")
var a *int
*a = 1
fmt.Println("赋值宕机后")
})
fmt.Println("运行后")
}
//运行结果
//运行前
//手动宕机前
//error: &{手动触发panic}
//赋值宕机前
//runtime error: runtime error: invalid memory address or nil pointer dereference
//运行后
panic 和 recover 的组合有如下特性:
- 《Go程序设计语言》
- 有 panic 没 recover,程序宕机
- 有 panic 也有 recover,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行
这部分内容就总结到这里,Get了不?