工作中,为了提高代码的性能,使用协程去处理相关逻辑的代码是随处可见的,但是有一点需要特别注意的就是,子协程中引发的panic
,如果没有recover
的话,是会引起整个服务端程序进程退出的。所以有一条准则,在每一个子协程内部,尽量都使用一下recover
。又因为recover
是需要和defer
搭配使用的,所以一般会在可能引发panic
的代码前面,使用defer
和recover
。
panic出现后,代码行为如下
- 逆序执行当前
goroutine
的defer
链条(recover
也正是从某个defer
中介入的) recover
的作用是,当程序出现panic
时,recover
能将其捕获,若捕获到panic
,则recover
会返回error
。- 若当前协程将
panic
捕获,则返回到上一级协程,如果panic
没有被当前函数堆栈内的任一defer
中的recover
捕获,此时打印错误信息和调用堆栈,然后调用exit(2)
结束整个进程
package main
import (
"fmt"
"time"
)
func divide(a, b int) int {
defer func() {
if err := recover(); err != nil {
fmt.Printf("捕获了panic,err:%v\n", err)
}
}()
res := a / b
fmt.Println("出现panic后,即使panic被捕获了,因为已经执行了defer,这里的代码执行不到了")
return res
}
func main() {
go divide(9, 0)
time.Sleep(5 * time.Second)
fmt.Println("因为子协程中的panic被捕获了,所以程序并没有退出,还能正常执行")
}
执行结果:
通过观察输出,就能知道运行流程如何了,有一点 需要注意的是,recover
不能直接跟在defer
后面,而是需要使用defer
后跟匿名函数的方式,将recover
放到匿名函数中,如果不这样做,会编译报错。
如果将recover
相关代码注释掉,则整个程序会因为没有捕获panic
而异常退出,如下
package main
import (
"fmt"
"time"
)
func divide(a, b int) int {
//defer func() {
// if err := recover(); err != nil {
// fmt.Printf("捕获了panic,err:%v\n", err)
// }
//}()
res := a / b
fmt.Println("出现panic后,即使panic被捕获了,因为已经执行了defer,这里的代码执行不到了")
return res
}
func main() {
go divide(9, 0)
time.Sleep(5 * time.Second)
fmt.Println("因为子协程中的panic被捕获了,所以程序并没有退出,还能正常执行")
}
从如下代码可以看出,子协程中的panic
不会传递给父协程,子协程内没有捕获,就直接panic
了
package main
import (
"fmt"
"time"
)
func divide(a, b int) int {
//defer func() {
// if err := recover(); err != nil {
// fmt.Printf("捕获了panic,err:%v\n", err)
// }
//}()
res := a / b
fmt.Println("出现panic后,即使panic被捕获了,因为已经执行了defer,这里的代码执行不到了")
return res
}
func main() {
defer func() {
fmt.Println("defer")
if err := recover(); err != nil {
fmt.Printf("捕获了panic,err:%v\n", err)
}
}()
go divide(9, 0)
time.Sleep(5 * time.Second)
fmt.Println("因为子协程中的panic被捕获了,所以程序并没有退出,还能正常执行")
}
输出:
可以看到main
方法中的defer
都没有执行到就panic
退出了
如果子协程内部捕获了panic
,则main
方法中的defer
是会正常执行的,如下:
package main
import (
"fmt"
"time"
)
func divide(a, b int) int {
defer func() {
if err := recover(); err != nil {
fmt.Printf("捕获了panic,err:%v\n", err)
}
}()
res := a / b
fmt.Println("出现panic后,即使panic被捕获了,因为已经执行了defer,这里的代码执行不到了")
return res
}
func main() {
defer func() {
fmt.Println("defer")
if err := recover(); err != nil {
fmt.Printf("捕获了panic,err:%v\n", err)
}
}()
go divide(9, 0)
time.Sleep(5 * time.Second)
fmt.Println("因为子协程中的panic被捕获了,所以程序并没有退出,还能正常执行")
}