1. panic:
一般表示程序出现了严重故障,此时程序不能继续运行,比如服务挂了...
产生方式/场景:
(1) 主动调用:通过 panic() 函数;
(2) 被动调用:panic产生后,会将堆栈信息抛出来,方便定位问题。
例如:程序在运行阶段发生了内存异常操作,例如:空指针的取值,索引越界,栈溢出,改写只读内存... 等等(还有其他情况会导致panic,借鉴简书大佬的链接:Go 常见的引发 panic 的情况)
注意点:
1. panic 其实是一个终止函数栈执行的过程,但是在函数退出前都会执行defer里面的函数,直到所有的函数都退出后,才会执行panic
2. recover:
recover是go提供的一个用来截获 panic 信息,重新获取协程控制的函数。
使用要求:
1)recover必须在defer函数中使用,但是不能被 defer 直接调用,如:
defer recover() // 直接调用,这样的写法不能恢复panic
2)recover只能恢复同一个协程中的panic,所以必须与可能发生panic的协程在同一个协程中才生效
错误示例:
panic在子协程中,而recover在主协程中,recover不能跨协程捕获panic信息,最终会导致所有的协程全部挂掉,程序会整体退出
package main import ( "fmt" "runtime/debug" "time" ) // 错误示例:recover不能跨协程捕获panic信息 func Test() { panic("Test(): 我异常了...溜了溜了") } func main() { defer func() { err := recover(); if err != nil { debug.PrintStack() //fmt.Println(string(debug.Stack())) } }() go Test() // recover不能跨协程捕获 // Test() time.Sleep(5 * time.Second) // 模拟执行耗时任务(顺便等待子协程执行) fmt.Println("main()不能正常执行...") // panic后,主协程main()无法继续正常执行打印 }
错误示例 - 运行结果:
编辑
正确示例:
package main import ( "fmt" "runtime/debug" "time" ) // 正确示例: func Test() { defer func() { if err := recover(); err != nil { debug.PrintStack() //fmt.Println(string(debug.Stack())) } }() panic("Test(): 我异常了...溜了溜了") } func main() { go Test() // Test() time.Sleep(5 * time.Second) // 模拟执行耗时任务(顺便等待子协程执行) fmt.Println("main()依然是能正常执行的...") // 可以正常打印,即使Test()发生panic }
正确示例 - 运行结果:
编辑
3. 函数panic后,打印堆栈调用信息:
程序发生panic后,可以将堆栈信息打印出来,方便定位问题:
debug.PrintStack() 或 fmt.Println(string(debug.Stack()))
注意点:
1. 正确使用recover(),要记住recover只能恢复当前协程的panic,否则还是会导致整个进程挂掉
2. recover并非万能的,它只对用户态下的panic关键字有效
实际上在Go语言中,是存在着一些无法恢复的“恐慌”事件的,如fatalthrow方法、fatalpanic方法等,这些都是直接通过调用 exit() 方法进行中断的,属于无法恢复的“恐慌”事件,比如:
对于go1.6以上版本,如果出现 并发map读写 程序会直接以 fatal error 崩溃,即使在同一个协程内有recover()也不能恢复
- 如果map由多协程同时读和写就会出现 fatal error:concurrent map read and map write 的错误
- 多个协程同时写会出现 fatal error: concurrent map writes 的错误
我写博客是真的慢...写了好久~