panic和recover
panic
和recover
翻译成中文是恐慌
和恢复
,我个人感觉比较别扭,还是直接用英文吧。
什么是panic
panic
Golang中处理错误的常规方式是使用前面我们讨论的error
。但是有一些比较特殊的场景,发生错误后我们的程序就无法运行下去了。这时我们通过panic
来终止程序的执行。当一个函数中发生panic
时,该函数停止继续执行,然后该函数中被defer
的函数执行,虽然程序执行控制权转移给外层函数。该过程会冒泡直到goroutine
级别。然后打印panic
信息和调用栈并终止整个程序的执行。现在看起来可能有点晦涩难懂,后面我们通过几个简单的例子看下应该会清晰很多。
如果发生了panic
,我们可以通过recover
来恢复程序的执行,panic
和recover
的关系就像其他编程语言中的try throw catch
。
什么场景使用panic
panic
我们通常应该使用error
来处理异常情况,除非程序无法继续运行下去了再使用panic
和recover
。
以下两种情况比较适合使用panic
:
有异常情况导致程序无法继续运行下去。
比如我们要实现web服务器,如果端口绑定失败,就可以生成一个
panic
。编程错误。
比如我们期望一个函数的入参为指针,如果传进来的是
nil
,那么就是编程实现逻辑错误,这时可以生成一个panic
。
panic
举例
panic
举例Golang中panic
函数的签名如下:
func panic(interfce{})
传给panic
函数的入参会在程序终止时打印出来。我们通过例子来理解下。
package main
import "fmt"
func fullName(firstName *string, lastName *string) {
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
该程序用来打印一个人的姓名的全称。函数fullName
中判断了firstName
和lastName
是否是nil
。如果是nil
就调用了panic
方法,调用时传入了一个字符串用来表示错误信息。该错误信息会在程序终止时打印,该程序执行结果如下:
panic: runtime error: last name cannot be nil
goroutine 1 [running]:
main.fullName(0xc000074f68, 0x0)
/Users/goWorkspace/src/test/main.go:10 +0x145
main.main()
/Users/goWorkspace/src/test/main.go:18 +0x4d
exit status 2
我们分析下这个打印结果来探究下panic
的工作原理。我们调用fullName
时第二个参数传的是nil
,因此fullName
内执行了panic
语句。Golang程序执行过程中如果遇到panic
就会终止执行,并打印传给panic
的参数和调用栈。由于panic
是发生在fullName
函数内,因此一次打印了main.fullName(0xc000074f68, 0x0)
和main.main()
。
defer
+ panic
defer
+ panic
如果一个函数内发生了panic
,那么会先执行defer
调用的函数,然后再转移到外层函数。该过程会不断向外冒泡直到goroutine
级别。然后打印panic
信息和调用栈。 前面的例子中没有defer
函数调用。如果有defer
函数调用,那么会先执行defer
调用的函数,然后程序执行控制权再转移到外层函数。下面我们增加个defer
调用语句。
package main
import "fmt"
func fullName(firstName *string, lastName *string) {
defer fmt.Println("deferred call in fullName")
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
该程序执行结果如下:
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nil
goroutine 1 [running]:
main.fullName(0xc000074f58, 0x0)
/Users/work/goWorkspace/src/test/main.go:11 +0x1d1
main.main()
/Users/work/goWorkspace/src/test/main.go:20 +0xa5
exit status 2
这段程序中fullName
发生panic
时先执行被defer
的函数调用,打印了deferred call in fullName
。然后程序执行控制权转移给外层函数,接着执行外层函数被defer
的函数调用,打印了deferred call in main
。此时已到goroutine
级别,因此不会再接着向外冒泡了。接着打印了panic
信息以及调用栈。
recover
recover
recover
跟panic
一样,也是Golan内置函数。recover
一般在被defer
的函数内部调用,用来将程序恢复到正常。panic
和recover
跟其他编程需要中的try catch
非常像,一个用来抛错误,一个用来处理错误并恢复程序的正常执行。recover
函数签名如下:
func recover() interface{}
下面我们通过例子看下recover
是如何将panic
的程序恢复到正常执行的。
package main
import "fmt"
func recoverName() {
if r := recover(); r != nil {
fmt.Println("recovered from ", r)
}
}
func fullName(firstName *string, lastName *string) {
defer recoverName()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}
func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
这里我们在被defer
的函数中调用了recover
来将panic
恢复到正常。注意recover()
函数的调用结果即为panic
的参数。这里我们拿到panic
的参数后只是进行了打印。该程序执行结果如下:
recovered from runtime error: last name cannot be nil
returned normally from main
deferred call in main
recover
只能恢复当前goroutine
的panic
recover
只能恢复当前goroutine
的panic
recover
只能恢复当前goroutine
的panic
,不能恢复其他gorutine
的panic
。
package main
import (
"fmt"
"time"
)
func recovery() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}
func a() {
defer recovery()
fmt.Println("Inside A")
go b()
time.Sleep(1 * time.Second)
}
func b() {
fmt.Println("Inside B")
panic("oh! B panicked")
}
func main() {
a()
fmt.Println("normally returned from main")
}
这里在goroutine
b
中发生了panic
,但是函数a
的执行位于主goroutine
,跟b
不是一个,因此函数a
中的recover
无法恢复b
中的panic
。该程序执行结果如下:
Inside A
Inside B
panic: oh! B panicked
goroutine 5 [running]:
main.b()
/Users/work/goWorkspace/src/test/main.go:23 +0x79
created by main.a
/Users/work/goWorkspace/src/test/main.go:17 +0x95
exit status 2
如果b
函数调用不是在一个新的goroutine
,比如我们将go b()
改为b()
,则执行结果如下:
Inside A
Inside B
recovered: oh! B panicked
normally returned from main
这时b
和a
的执行都位于主goroutine
,因此可以正常恢复。
运行时panic
panic
在Golang中,有一些运行时错误也会引发panic
,比如数组访问越界。这种情况相当于我们手动调用panic
,并传入runtime.Error类型的参数。接口runtime.Error
定义如下:
type Error interface {
error
// RuntimeError is a no-op function but
// serves to distinguish types that are run time
// errors from ordinary errors: a type is a
// run time error if it has a RuntimeError method.
RuntimeError()
}
从其定义能够看出来,runtime.Error
接口也实现了error
接口。我们通过例子来看下运行时panic
。
package main
import "fmt"
func a() {
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}
func main() {
a()
fmt.Println("normally returned from main")
}
这里我们访问切片n
的索引为3
的位置,显然越界了。因此引发运行时panic
。该程序输出如下:
panic: runtime error: index out of range
goroutine 1 [running]:
main.a()
/Users/work/goWorkspace/src/test/main.go:7 +0x11
main.main()
/Users/work/goWorkspace/src/test/main.go:11 +0x22
exit status 2
运行时panic
也可以通过recover
来恢复。
package main
import "fmt"
func r() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
}
}
func a() {
defer r()
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}
func main() {
a()
fmt.Println("normally returned from main")
}
这里我们在被defer
的函数r
中将panic
的程序恢复正常。该程序输出为:
Recovered runtime error: index out of range
normally returned from main
recover
后打印调用栈
recover
后打印调用栈从前面的几个例子我们会发现,程序recover
后调用栈就不会被自动打印了。不过我们可以借助Debug包的PrintStack函数来手动打印调用栈。
package main
import (
"fmt"
"runtime/debug"
)
func r() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
debug.PrintStack()
}
}
func a() {
defer r()
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}
func main() {
a()
fmt.Println("normally returned from main")
}
这里我们通过debug.PrintStack()
来手动打印了调用栈。该程序输出如下:
Recovered runtime error: index out of range
goroutine 1 [running]:
runtime/debug.Stack(0xc00007a008, 0xc00006ee08, 0x2)
/usr/local/go/src/runtime/debug/stack.go:24 +0xa7
runtime/debug.PrintStack()
/usr/local/go/src/runtime/debug/stack.go:16 +0x22
main.r()
/Users/work/goWorkspace/src/test/main.go:11 +0x9c
panic(0x10a9bc0, 0x115d560)
/usr/local/go/src/runtime/panic.go:513 +0x1b9
main.a()
/Users/work/goWorkspace/src/test/main.go:18 +0x3e
main.main()
/Users/work/goWorkspace/src/test/main.go:23 +0x22
normally returned from main
这里我们先恢复了程序的正常,然后紧接着手动打印了调用栈。
Last updated
Was this helpful?