goroutine配上panic会怎样?

跨界架构师

共 2240字,需浏览 5分钟

 ·

2022-02-20 10:21

这里是Z哥的个人公众号

每周五11:45 按时送达

当然了,也会时不时加个餐~

我的第「220」篇原创敬上


大家好,我是Z哥。


最近用 Golang 进行编码也有3个月了,说来惭愧,到现在还没正儿八经深入学习一下 Golang,一直被工作赶着往前在跑。


最近正好在工作中遇到一个问题,需要对 Golang 中的 goroutine 和 panic & recover 稍做深入的了解,算是忙里偷闲学习一下。



对 goroutine 的底层细节就不展开了,网上有不少相关的文章解读,如果你愿意的话,也可以去扒一下 Golang 的源码。


简单对 goroutine 进行一下概括就是:


goroutine 实现了 M:N 的线程模型,是协程的一种实现。golang 内置的调度器,可以让多核 CPU 中每个 CPU 执行一个协程。


单从表现来看,你可以将 goroutine 看作是 java 之类编程语言中的多线程的运行效果。


好了,那么问题来了:goroutine 中发生 panic 会怎样?


话不多说,实践是检验真理的唯一标准,我们直接上手 coding。


func main()  {

go panicInGoroutine()


//以下3行代码是为了让控制台挂起,等待gorouine运行完毕。

fmt.Println("wait")

input := bufio.NewScanner(os.Stdin)

input.Scan()

}


func panicInGoroutine()  {

panic("panic in goroutine.")

}


运行代码的结果如下:


292e7915448138e96b96b4518cc10b69.webp


可以看到,整个程序都崩了。


那么,如果在 goroutine 里的 goroutine 发出 panic 呢?也是一样的效果,程序崩了。


可能你会觉得整个程序之所以会崩,是因为异常被层层上抛到主线程导致的,其实并非如此。在 Golang 中,任何地方发生的任意一个 panic,都会直接程序退出。



那么怎么才能让程序不退出呢?


通过调用 recover() 方法来捕获 panic 并恢复将要崩掉的程序。


func main() {

go panicInGoroutine()


//以下3行代码是为了让控制台挂起,等待gorouine运行完毕。

fmt.Println("wait")

input := bufio.NewScanner(os.Stdin)

input.Scan()

}


func panicInGoroutine() {

//recover()必须要和defer配合一起用,确保一旦执行到该方法体,这里定义的defer方法一定会被执行,哪怕是发生了panic。

defer func() {

err := recover()

if err != nil {

fmt.Printf("recover receive a err: %+v \n", err)

}

}()


panic("panic in goroutine.")

}


执行上面的代码,结果如下:


330732b5a9c8d6735a1143bf2dc20dc2.webp


可以看到,程序没有再崩了。那么新的问题又来了,能不能把 recover() 放到最外层的方法里,这样可以更好地实现一次 recover() 覆盖当前方法其下所有的 panic。


func main() {

        defer func() {

err := recover()

if err != nil {

fmt.Printf("recover receive a err: %+v \n", err)

}

}()


go panicInGoroutine()


//以下3行代码是为了让控制台挂起,等待gorouine运行完毕。

fmt.Println("wait")

input := bufio.NewScanner(os.Stdin)

input.Scan()

}


func panicInGoroutine() {

panic("panic in goroutine.")

}


运行之后的结果:


889cc62032a2ff0237ad656774c76688.webp


竟然还是崩了。如果你是一位 Java 或者 .Net 的程序员习惯了 try-catch-finally 的运行效果肯定对这个结果比较意外。在父方法定义的 recover() 竟然无法捕获到子方法里的 panic。


其实这里的原因是,外层方法中定义的 recover() 无法捕获通过 goroutine 执行的子方法中抛出的 panic。在上面的代码中,我们把 go panicInGoroutine() 前面的 go 去掉就可以正常捕获了。


b89314cf70be228046cb91e11231c75f.webp


好了,那么根据以上这些信息得到的处理 panic 的正确姿势是什么呢?


  1. 必须通过 defer 关键字来调用 recover()。

  2. 当通过 goroutine 调用某个方法,一定要确保内部有 recover() 机制。


如果你想进一步深入了解 panic 和 recove r的机制,分享你一个超棒的硬核视频:https://www.bilibili.com/video/BV155411Y7XT,第一遍看可能会有点晕,建议反复看,直到完全理解其原理。



推荐阅读:


原创不易,如果你觉得这篇文章还不错,就「点赞」或者「在看」一下吧,鼓励我的创作 :)

也可以分享我的公众号名片给有需要的朋友们。

如果你有关于软件架构、分布式系统、产品、运营的困惑

可以试试点击「阅读原文

浏览 40
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报