panic是go语言在运行时发生的错误,如数组访问越界、空指针引用等,这些运行时错误会引起程序崩溃。这并不是我们想看到的,程序崩溃可能造成体验停止、服务中断,就像没有人希望在马桶冲水冲到一半时坏掉。
我们一般用error来处理go语言中遇到的错误。Errors能满足我们大部分时候的开发需求
但有情况下,程序在发生异常后无法继续运行下去,这时我们用panic终止程序。
当一个函数发生了panic,他所执行的内容会立刻停止,并且defer过的函数紧接着被执行,最后return回该函数的调用者。这个过程会一直进行直到整个 goroutines的函数都返回了,此时程序将打印panic信息,然后打印调用栈,终止。
稍后的例子会清晰地描述这一过程
有一点应该明白的是,我们应该避免程序panic或recover,尽可能的使用error。仅在程序无法继续执行的情况下才应使用panic和recover机制。
有两种情况需要使用panic
一个程序遇到后不可继续执行的错误。
举个例子,web server无法绑定请求端口。这种情况下不panic就什么也执行不下去
写程序出错
假设有一个接受指针为参数的方法,有人用nil作为参数去调用它,在这种情况下会产生panic。
内置panic函数签名:
func panic(interface{})
传入panic的参数会被打印到终端。
上代码
package main
import (
"fmt"
)
func div(a,b int) int {
return a/b
}
func main() {
result:=div(3, 0)
fmt.Println(result)
fmt.Println("returned normally from main")
}
输出
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.div(…)
/tmp/sandbox624380051/prog.go:8
main.main()
/tmp/sandbox624380051/prog.go:12 +0x20
上面的例子在运行到12行的时候出现了除数为0的情况,于是调用panic后直接执行最外层函数也即是main函数的rerturn,所以能看到并没有执行第14行的代码就退出了,并打印了跟踪栈。
在整个goroutine中,能看到/tmp/sandbox624380051/prog.go:8
首先在第8行触发了panic,下面是/tmp/sandbox624380051/prog.go:12
,说明是在这里调用了上面的代码导致panic。到此,已经没有外层的函数调用,栈跟踪打印完毕。
让我们看一下panic都做了什么,当一个函数发生了panic,他便立刻停止,接着任何被defer执行的函数开始执行然后return回调用该函数的调用者。这一过程会一直持续到所有goroutine都return了为止。此时程序打印panic消息,然后打印跟踪栈,最后终止。
再上一个例子中我们没有使用defer。这次我们做一些修改,使用defer语句
package main
import (
"fmt"
)
func div(a,b int) int {
defer fmt.Println("deferred call in div")
return a/b
}
func main() {
defer fmt.Println("deferred call in main")
result:=div(3, 0)
fmt.Println(result)
fmt.Println("returned normally from main")
}
输出
deferred call in div
deferred call in main
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.div(0x3, 0x0, 0x0, 0x104560)
/tmp/sandbox664019576/prog.go:9 +0x140
main.main()
/tmp/sandbox664019576/prog.go:14 +0xa0
上面的例子只在第8、13行添加defer执行函数。在程序panic时,首先执行当前函数中的defer,然后返回到该函数的调用者中再执行defer,以此类推直到顶级调用者为止。
recover仅当在同一goroutine时才有效,无法recover其他goroutine发生的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")
}
在上面的例子中,函数b() 在第23行发生panic。函数a() 调用被defer的函数recovery() (捕获panic的作用),函数b() 在另一个goroutine中17行被调用,下一行的sleep
只是为了保证程序不会在b() 还没执行完之前就退出。
你觉得会输出什么内容?panic会被捕获到吗?不会的,因为recover函数在不同的goroutine中,而触发panic的函数b() 在另外一个goroutine中,因此无法捕获panic
输出
Inside A
Inside B
panic: oh! B panicked
goroutine 6 [running]:
main.b()
/tmp/sandbox238905465/prog.go:23 +0xa0
created by main.a
/tmp/sandbox238905465/prog.go:17 +0xc0
从结果可以看到recover并未生效
如果函数b()在同一个goroutine中调用,则panic能被捕获到。
将上面的代码17行go b()
改为b()
,就能保证实在同一goroutine发生,recover可以生效,运行后会输出
Inside A
Inside B
recovered: oh! B panicked
normally returned from main
panic也可能是因为运行时引起的,例如数组越界访问,等于一个接口类型的 runtime.Error
调用了panic,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
接口
我们写一个人为runtime 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: runtime error: index out of range [3] with length 3
goroutine 1 [running]:
main.a()
/tmp/sandbox935148947/prog.go:9 +0x20
main.main()
/tmp/sandbox935148947/prog.go:13 +0x20
当然了,这个情况也是可以被捕获到的,修改下代码
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")
}
从输出中可以看到panic被recover了
Recovered runtime error: index out of range [3] with length 3
normally returned from main
我们recover了panic,但丢失有关panic的堆栈跟踪信息。
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")
}
上面的代码11行debug.PrintStack()
打印了跟踪栈
Recovered runtime error: index out of range [3] with length 3
goroutine 1 [running]:
runtime/debug.Stack(0x3e, 0x0, 0x0, 0x54b3)
/usr/local/go/src/runtime/debug/stack.go:24 +0xc0
runtime/debug.PrintStack()
/usr/local/go/src/runtime/debug/stack.go:16 +0x20
main.r()
/tmp/sandbox553506768/prog.go:11 +0xe0
panic(0x10cc60, 0x40e020)
/usr/local/go/src/runtime/panic.go:679 +0x240
main.a()
/tmp/sandbox553506768/prog.go:18 +0x60
main.main()
/tmp/sandbox553506768/prog.go:23 +0x20
normally returned from main
从输出可以了解到首先recover了runtime panic,接着打印了跟踪栈,在recover以后,打印了normally returned from main正常结束了函数。