go 语言的宕机恢复(recover)

go 语言中的三种报错机制

  • 第一种,也就是常说的 error 类型错误,不需要 recover 拯救,处理方式自己决定

  • 第二种,panic 和 recover 是紧密集合的,有点类似 try catch,recover 能捕获到 panic

  • 第三种,一些 Go 语言系统级别的错误,比如发生死锁,数据竞争,这种错误程序会立刻报错,无法 recover

recove 的作用

go 语言中,错误一般会由 error 触发,但是如果比较严重的错误(通常是没有恰当处理的 error,也可是手动触发) 会造成 panic 。 一旦主程序 panic ,会导致整个程序挂掉。如果这个错误不是那么严重,我们希望程序可以继续往下执行,而不是整个程序挂掉。

  1. recover 函数,对 panic 错误进行拦截,避免上传给主函数,进而避免整个程序挂掉。
  2. 可以在程序崩溃前,做一些操作,举个例子,当 web 服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭,如果不做任何处理,会使得客户端一直处于等待状态。

下面例子

如果给 out 函数传入两个相同的形参,就会引发 panic

如果没有 recover 拦截,fmt.Print 这行是执行不到的。

func main() {
	Out(1, 1)
	fmt.Println("*******此行函数依然能继续执行******")
}
func Out(numb1, numb2 int) bool {
	defer func() {
		if r := recover(); r != nil {    # 在此处对panic进行拦截,不会将错误继续上报上去。
			fmt.Println("异常已扑捉,避免继续往上层传递")    
		}
	}()
	if numb1 == numb2 {
		panic("两个数不能相等")
	}
	return numb1 > numb2
}

知识点

  1. recover 仅在延迟函数 defer 中有效
  2. 因为 Go 语言没有异常系统,其使用 panic 触发宕机类似于其他语言的抛出异常,recover 的宕机恢复机制就对应其他语言中的 try/catch 机制。
  3. 谨记一点 recover 只能恢复本协程的 panic

注意:即使是子协程内引发的 panic 依然会导致主程序的挂掉,如下面的例子

func main() {
	go OutOne()
	go OutTwo()
	time.Sleep(time.Minute)
}

func OutOne() {
	panic("错误")
}
func OutTwo() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Print("****此处继续执行***")
	}
}

进阶

那么上面情况该怎么解决?

其实就是在发生 panic 的协程里面,用 recover 进行拦截。让它传不到主函数。

func main() {
	go OutOne()
	go OutTwo()
	time.Sleep(time.Minute)
}

func OutOne() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("OutOne 的报错已经别扑捉", r)
		}
	}()
	panic("错误")
}
func OutTwo() {
	for i := 0; i < 10; i++ {
		time.Sleep(time.Second)
		fmt.Println("****此处继续执行***")
	}
}

当panic、defer和子函数混合使用时的执行顺序

func f() {
	fmt.Println("打开文件b")
	defer func() {
		fmt.Println("关闭文件b")
	}()
	panic("文件b读取异常")
}

func main() {
	fmt.Println("打开文件a")
	defer func() { fmt.Println("关闭文件a") }()
	f()
	fmt.Println("文件正常执行")
}

# 输出
打开文件a
打开文件b
关闭文件b
关闭文件a
panic: 文件b读取异常
  • 记住这个例题,当 go 语言发生panic时,会首先执行已经“进栈”的 defer函数,最后然后在报出panic的错误。传给上层的函数。
  • 当子函数发生 panic的时候,函数依然是会先执行已经“进栈”的 defer函数,而爆出 panic

defer

go的代码中,defer 有点类似于压栈操作,只有被执行到了,才会入栈(延迟执行),但是实际上,defer函数是编译过程中,通过调整代码顺序实现

错误日志如何查看

package main

import "log"

func main() {
	err := funcA()
	if err != nil {
		log.Println("日志输出错误")
	}
}

func funcA() error {
	panic("错误")
}

输出:

  • 观察错误输出顺序,错误就是一个入栈的过程,会从底到上的打印,直到遇到 recover
  • 日志能不能输出这条错误,要看这条日志能不能走到。

参考文献

  1. https://www.zhihu.com/question/371695315/answer/1026803101

  2. http://c.biancheng.net/view/64.html

posted @ 2022-01-07 22:23  沧海一声笑rush  阅读(1361)  评论(0编辑  收藏  举报