Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

panic和recover #52

Open
kevinyan815 opened this issue Feb 1, 2021 · 0 comments
Open

panic和recover #52

kevinyan815 opened this issue Feb 1, 2021 · 0 comments

Comments

@kevinyan815
Copy link
Owner

kevinyan815 commented Feb 1, 2021

Go 语言中两个经常成对出现的两个关键字 — panic 和 recover。这两个关键字与上一节提到的 defer 有紧密的联系,它们都是 Go 语言中的内置函数,也提供了互补的功能。

  • panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 goroutine 中递归执行调用方的 defer
  • recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用

panic和recover的一些行为现象

  • panic 只能触发当前 goroutine 的 defer;
  • recover 只有在 defer 中调用才会生效;
  • panic 允许在 defer 中嵌套多次调用;

下面通过几个例子了解一下使用 panic 和 recover 关键字时遇到的这几个现象

panic 只能触发当前 goroutine 的 defer

panic 只会触发当前 Goroutine 的延迟函数调用,我们可以通过如下所示的代码了解该现象:

func main() {
	defer println("in main")
	go func() {
		defer println("in goroutine")
		panic("")
	}()

	time.Sleep(1 * time.Second)
}

$ go run main.go
in goroutine
panic:
...

当我们运行这段代码时会发现 main 函数中的 defer 语句并没有执行,执行的只有当前 Goroutine 中的 defer。

前面我们曾经介绍过 defer 关键字对应的 runtime.deferproc 会将延迟调用函数与调用方所在 goroutine 进行关联。所以当程序发生崩溃时只会调用当前 goroutine 的延迟调用函数也是非常合理的。

goroutine1 =====> _defer ---> _defer -> _defer

goroutine2 =====> _defer ---> _defer -> _defer

goroutine3 =====> _defer ---> _defer -> _defer

像上面示意这样,每个 goroutine 都有自己的延迟调用链,相互之间没有太多的关联,一个 goroutine 在 panic 时也不应该执行其他 goroutine 的延迟函数。

recover 只有在 defer 中调用才会生效

初学 Go 语言的读者可能会写出下面的代码,在主程序中调用 recover 试图中止程序的崩溃,但是从运行的结果中我们也能看出,下面的程序没有正常退出。

func main() {
	defer fmt.Println("in main")
	if err := recover(); err != nil {
		fmt.Println(err)
	}

	panic("unknown err")
}

$ go run main.go
in main
panic: unknown err

goroutine 1 [running]:
main.main()
	...
exit status 2

仔细分析一下这个过程就能理解这种现象背后的原因,recover 只有在发生 panic 之后调用才会生效。然而在上面的控制流中,recover 是在 panic 之前调用的,并不满足生效的条件,所以我们需要在 defer 中使用 recover 关键字。

func main() {
	defer func() {
		fmt.Println("defer in main")
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()

	panic("unknown err")
}

嵌套panic

Go 语言中的 panic 是可以多次嵌套调用的。一些熟悉 Go 语言的读者很可能也不知道这个知识点,如下所示的代码就展示了如何在 defer 函数中多次调用 panic:

func main() {
	defer fmt.Println("in main")
	defer func() {
		defer func() {
			panic("panic again and again")
		}()
		panic("panic again")
	}()

	panic("panic once")
}

$ go run main.go
in main
panic: panic once
	panic: panic again
	panic: panic again and again

goroutine 1 [running]:
...
exit code 2

从上述程序输出的结果,我们可以确定程序多次调用 panic 也不会影响 defer 函数的正常执行,所以使用 defer 进行收尾工作一般来说都是安全的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant