原文 golangbot.com/panic-and-r… 本文删除了一些废话 做一下精简
一般用error就够了
啥时候panic?
func panic(interface{})
func recover() interface{}
There are two valid use cases for panic.
- An unrecoverable error where the program cannot simply continue its execution. One example is a web server that fails to bind to the required port. In this case, it’s reasonable to panic as there is nothing else to do if the port binding itself fails.
- A programmer error. Let’s say we have a method that accepts a pointer as a parameter and someone calls this method using a
nilargument. In this case, we can panic as it’s a programmer error to call a method withnilargument which was expecting a valid pointer.
when a funciton has panic, what will happen?
When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates.
example1. nil pointer
func fullName(firstName *string, lastName *string) {
defer func() {
fmt.Println("defer msg from panic func")
}()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Println("returned normally from fullName")
}
func test() {
go func() {
defer func() {
fmt.Println("defer from another goroutine")
}()
fmt.Println("message from another goroutine")
}()
firstName := "Elon"
go fullName(&firstName, nil)
time.Sleep(10 * time.Second)
fmt.Println("returned normally from main")
}
这里需要注意,当一个goroutine中发生panic 另一个goroutine如果在panic之前就执行了,就没啥,负责就会被挂掉。
example2 runtime panic (out range of slice
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")
}
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
Defer Calls During a Panic
When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates.
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(0xc00006af28, 0x0)
/tmp/sandbox451943841/prog.go:13 +0x23f
main.main()
/tmp/sandbox451943841/prog.go:22 +0xc6
recover
func panic(interface{})
func recover() interface{}
Recover is useful only when called inside deferred functions.
If recover is called outside the deferred function, it will not stop a panicking sequence.
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 := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
recovered from runtime error: last name cannot be nil
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
Getting Stack Trace after Recover
If we recover from a panic, we lose the stack trace about the panic. Even in the program above after recovery, we lost the stack trace.
There is a way to print the stack trace using the PrintStack function of the Debug package
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 := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}
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, Recover and Goroutines
Recover works only when it is called from the same goroutine which is panicking. It’s not possible to recover from a panic that has happened in a different goroutine
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")
}
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
If the divide() function is called in the same goroutine, we would have recovered from the panic.