工作中,为了提高代码的性能,使用协程去处理相关逻辑的代码是随处可见的,但是有一点需要特别注意的就是,子协程中引发的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被捕获了,所以程序并没有退出,还能正常执行")
}

文章介绍了在Go语言编程中,使用协程时需要注意panic和recover的使用,以防止服务端程序因未捕获的panic而退出。通过示例代码展示了如何在子协程中正确使用defer和recover来捕获并处理panic,以及panic如何影响协程和父协程的执行流程。
1261

被折叠的 条评论
为什么被折叠?



