Go:Panic与Recover
1.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 := "SS"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
运行输出:
panic: runtime error: last name cannot be nil
goroutine 1 [running]:
main.fullName(0xc00006af58, 0x0)
/tmp/sandbox210590465/prog.go:12 +0x193
main.main()
/tmp/sandbox210590465/prog.go:20 +0x4d
从例子中可以看到,当给firstName分配值并未给lastName分配,调用fullName,当执行到 lastName == nil 时,程序会被终止,传递给panic函数的参数被打印出来,然后是堆栈信息 。由于程序在执行 panic 函数调用之后终止,后面代码将不再执行。
所以该程序首先打印传递给panic
函数的消息
panic: runtime error: last name cannot be nil
然后打印堆栈信息,根据堆栈跟踪,一项一项的打印。
2.例2
package main
import (
"fmt"
)
func slicePanic() {
n := []int{5, 7, 4}
fmt.Println(n[4])
fmt.Println("normally returned from a")
}
func main() {
slicePanic()
fmt.Println("normally returned from main")
}
例子中试图访问切片中n[4]
的无效索引,则该程序将有以下输出:
panic: runtime error: index out of range [4] with length 3
goroutine 1 [running]:
main.slicePanic()
/tmp/sandbox942516049/prog.go:9 +0x1d
main.main()
/tmp/sandbox942516049/prog.go:13 +0x22
3.在Panic中使用defer
当一个函数遇到panic时,它的执行会停止,但是任何defer的函数都会被执行,然后控制权返回给它的调用者。这个过程一直持续到当前 goroutine 的所有函数都已经返回,此时程序会打印panic消息,然后是堆栈跟踪,然后终止。
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 := "SS"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
上例唯一更改是添加了defer函数调用,现在的输出为:
deferred call in fullName
deferred call in main
panic: runtime error: last name cannot be nil
goroutine 1 [running]:
main.fullName(0xc00006af28, 0x0)
/tmp/sandbox451943841/prog.go:13 +0x23f
main.main()
/tmp/sandbox451943841/prog.go:22 +0xc6
当程序遇到panic时,首先执行defer的函数调用,然后控制返回到执行defer调用的调用者,依此类推。 最后是堆栈跟踪信息。
4.panic中使用recover
仅当在defer函数内部调用时,recover才有用。在defer函数内执行recover恢复正常执行来停止panic,并检索传递给panic函数调用的错误消息。如果在defer函数之外调用recover,并不会停止panic。
package main
import (
"fmt"
)
func recoverFullName() {
if r := recover(); r!= nil {
fmt.Println("recovered from ", r)
}
}
func fullName(firstName *string, lastName *string) {
defer recoverFullName()
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 := "SS"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
调用recover()
返回传递给panic
函数调用的值, 当fullName
函数调用时,recoverFullName()
将调用defer函数,用于recover()
停止panic,打印结果
recovered from runtime error: last name cannot be nil
returned normally from main
deferred call in main
当程序在 lastName == nil 发生panic时。recoverFullName
函数被调用,该函数又调用recover()
以重新获得对panic的控制。并将返回值传递给的panic()
,因此它打印:
recovered from runtime error: last name cannot be nil
执行后recover()
,panic停止,控制权返回给调用者,在这种情况下,是main
函数。程序从 fmt.Println(“returned normally from main”) 开始继续正常执行,main的
panic已经recover了。它打印returned normally from main
后跟deferred call in main
package main
import (
"fmt"
)
func recoverInvalidAccess() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
}
}
func invalidSliceAccess() {
defer recoverInvalidAccess()
n := []int{5, 7, 4}
fmt.Println(n[4])
fmt.Println("normally returned from a")
}
func main() {
invalidSliceAccess()
fmt.Println("normally returned from main")
}
运行上面的程序会输出
Recovered runtime error: index out of range [4] with length 3
normally returned from main
5.recover后获取stack跟踪
package main
import (
"fmt"
"runtime/debug"
)
func recoverFullName() {
if r := recover(); r != nil {
fmt.Println("recovered from ", r)
debug.PrintStack()
}
}
func fullName(firstName *string, lastName *string) {
defer recoverFullName()
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 := "SS"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
在上面的例子中,使用debug.PrintStack()
打印堆栈信息。
recovered from runtime error: last name cannot be nil
goroutine 1 [running]:
runtime/debug.Stack(0x37, 0x0, 0x0)
/usr/local/go-faketime/src/runtime/debug/stack.go:24 +0x9d
runtime/debug.PrintStack()
/usr/local/go-faketime/src/runtime/debug/stack.go:16 +0x22
main.recoverFullName()
/tmp/sandbox771195810/prog.go:11 +0xb4
panic(0x4a1b60, 0x4dc300)
/usr/local/go-faketime/src/runtime/panic.go:969 +0x166
main.fullName(0xc0000a2f28, 0x0)
/tmp/sandbox771195810/prog.go:21 +0x1cb
main.main()
/tmp/sandbox771195810/prog.go:30 +0xc6
returned normally from main
deferred call in main
从输出中可以了解到panic被恢复并recovered from runtime error: last name cannot be nil
打印出来了之后,打印堆栈跟踪。然后在panic恢复后打印
returned normally from main
deferred call in main
6.panic、recover和Goroutines
Recover 仅在从恐慌的同一个 goroutine 中调用时才起作用。无法从不同 goroutine 中发生的panic中恢复过来。
package main
import (
"fmt"
)
func recovery() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}
func sum(a int, b int) {
defer recovery()
fmt.Printf("%d + %d = %d\n", a, b, a+b)
done := make(chan bool)
go divide(a, b, done)
<-done
}
func divide(a int, b int, done chan bool) {
fmt.Printf("%d / %d = %d", a, b, a/b)
done <- true
}
func main() {
sum(5, 0)
fmt.Println("normally returned from main")
}
在上面的例子中,该函数divide()
将发生panic。因为 b 是零并且零不能做除数。该sum()
函数调用一个defer函数recovery()
,用于从panic中恢复。该函数divide()
作为单独的 goroutine 调用,在done channel
等待,确保divide()
完成并执行。
该例中panic将无法recover。这是因为该recovery
函数存在于不同的 goroutine 中,而panic发生在divide()
不同 goroutine中的函数中。因此recover是不可能的。
这个例子打印如下:
5 + 0 = 5
panic: runtime error: integer divide by zero
goroutine 18 [running]:
main.divide(0x5, 0x0, 0xc0000a2000)
/tmp/sandbox877118715/prog.go:22 +0x167
created by main.sum
/tmp/sandbox877118715/prog.go:17 +0x1a9
可以从输出中看出panic并未recover。 如果divide()
在同一个 goroutine 中调用该函数,就会从panic中recover过来
如果将上面例子中 go divide(a, b, done)
改为 divide(a, b, done)
,panic将会被recover,因为panic发生在同一个 goroutine 中,则它将打印:
5 + 0 = 5
recovered: runtime error: integer divide by zero
normally returned from main
发表评论
要发表评论,您必须先登录。