有关defer,参见Golang中的defer
recover
使用panic抛出异常后, 将立即停止当前函数的执行并运行所有被defer的函数,然后将panic抛向上一层,直至程序crash。但是也可以使用被defer的recover函数来捕获异常阻止程序的崩溃,recover只有被defer后才是有意义的。
1 2 3 4 5 6 7 8 9 10 11
| func main() {
print(123)
print(456) panic("throw an error")
print(678)
}
|
结果:
1 2 3 4 5
| 123456panic: throw an error
goroutine 1 [running]: main.main() /Users/shuangcui/explore/panicandrecover.go:31 +0x67
|
使用recover()捕获异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func main() {
print(123)
defer func() { if err := recover(); err != nil { print("recover it") } }()
print(456) panic("throw an error")
print(678)
}
|
结果为:
如果有两个recover,则捕获异常的是后一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| func main() {
print(123)
defer func() { if err := recover(); err != nil { print("recover it") } }()
defer func() { if err := recover(); err != nil { print("复原!") } }()
print(456) panic("throw an error")
print(678)
}
|
结果为:
panic之后的任何代码都不会继续执行
前提是panic不在if里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package main
import "fmt"
func main() { defer_call() fmt.Println("333 Helloworld") }
func defer_call() { defer func() { fmt.Println("11111") }()
defer func() { fmt.Println("22222") }()
defer func() { if r := recover(); r != nil { fmt.Println("Recover from r : ", r) } }()
defer func() { fmt.Println("33333") }()
fmt.Println("111 Helloworld")
panic("Panic 1!")
panic("Panic 2!")
fmt.Println("222 Helloworld") }
|
输出为:
1 2 3 4 5 6
| 111 Helloworld 33333 Recover from r : Panic 1! 22222 11111 333 Helloworld
|
对于goroutine中的panic,协程外面的recover是无法恢复的;goroutine中的recover,同样无法恢复协程外的panic
但协程中的recover可以恢复协程中的panic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "time" )
func main() {
go func() { defer func() { if err := recover(); err != nil { fmt.Println("recover err:", err) } }() panic("里面出错了") }()
time.Sleep(1 * time.Second)
}
|
输出为:
recover err 里面出错了
主方法中的recover,也可以恢复子方法里的panic
但如果go subfunc(),则同样无法捕获subfunc中的异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func main() {
fmt.Println(123)
defer fmt.Println(999)
defer func() { if err := recover(); err != nil { fmt.Println("恢复异常:",err) }
}() subfunc()
}
func subfunc() {
defer fmt.Println(888) panic("出现了bug")
defer fmt.Println(456)
}
|
结果为:
1 2 3 4
| 123 888 恢复异常: 出现了bug 999
|
因为panic发生的时候,panic函数后面的语句都不会执行了,所以recover函数不能放在panic语句后面执行,而要放在defer函数中执行。
使用 panic 抛出异常后,函数执行将从调用 panic 的地方停止,如果函数内有 defer 调用,则执行 defer 后边的函数调用,如果 defer 调用的函数中没有捕获异常信息,这个异常会沿着函数调用栈往上传递,直到 main 函数仍然没有捕获异常,将会导致程序异常退出
如何区别使用 panic 和 error 两种方式?
惯例是:导致关键流程出现不可修复性错误的使用 panic ,其他使用 error 。
panic 和 recover 的组合有如下特性:
- 有 panic 没 recover ,程序宕机。
- 有 panic 也有 recover ,程序不会宕机,执行完对应的 defer 后,从宕机点退出当前函数后继续执行。
拓展&参考:
golang panic和recover 实现原理
Go 学习笔记(19)— 函数(05)[如何触发 panic、触发 panic 延迟执行、panic 和 recover 的关系]
Go 语言踩坑记——panic 与 recover
recover能捕获所有错误吗?
不能!
Go 有哪些无法恢复的致命场景?
像
- 并发读写 map
fatal error: concurrent map read and map write
- 堆栈内存耗尽(如递归)
1 2 3
| runtime: goroutine stack exceeds 1000000000-byte limit runtime: sp=0xc0200e1bf0 stack=[0xc0200e0000, 0xc0400e0000] fatal error: stack overflow
|
- 将 nil 函数作为 goroutine 启动
fatal error: go of nil func value
- goroutines 死锁
fatal error: all goroutines are asleep - deadlock!
- 线程超过设置的最大限制
fatal error: thread exhaustion
- 超出可用内存
fatal error: runtime: out of memory
总之 都会报fatal error:xxxxxxxx
原文链接: https://dashen.tech/2019/09/12/Go异常处理机制panic和recover/
版权声明: 转载请注明出处.