当前位置: 首页 > 科技观察

当Goroutine与Panic配对时会发生什么?

时间:2023-03-16 11:58:15 科技观察

大家好,我是Z哥,最近用Golang写代码3个月了。说来惭愧,到现在都没有认真学习过Golang,一直被工作驱使着。最近刚在工作中遇到一个问题,需要对Golang中的goroutine和panic&recover有一点深入的了解。我不会展开goroutine的底层细节。网上有很多相关的文章。如果你愿意,你也可以浏览Golang的源代码。goroutine的简单总结就是:goroutine实现了M:N线程模型,是coroutines的一种实现。Golang内置的调度器可以让多核CPU中的每个CPU执行一个协程。单从性能的角度,可以把goroutine看成是java等编程语言中多线程的效果。那么,问题来了:当goroutine发生panic时会发生什么?话不多说,实践是检验真理的唯一标准,那我们就直接上手编码吧。funcmain(){gopanicInGoroutine()//下面3行代码是挂起控制台,等待goroutine运行完毕。fmt.Println("wait")input:=bufio.NewScanner(os.Stdin)input.Scan()}funcpanicInGoroutine(){panic("panicingoroutine.")}代码运行结果如下:如您所见,整个程序崩溃了。那么,如果goroutine中的goroutinepanic了怎么办?效果一样,程序崩溃。你可能会认为,整个程序之所以崩溃,是因为异常被逐层抛给了主线程,但事实并非如此。在Golang中,任何地方发生panic都会直接退出程序。那么程序怎么会不退出呢?通过调用recover()方法来捕获恐慌并恢复即将崩溃的程序。funcmain(){gopanicInGoroutine()//下面3行代码是挂起控制台,等待goroutine运行完毕。fmt.Println("wait")input:=bufio.NewScanner(os.Stdin)input.Scan()}funcpanicInGoroutine(){//recover()必须和defer一起使用,保证一旦方法体执行完,这里定义的defer方法肯定会被执行,即使发生panic。deferfunc(){err:=recover()iferr!=nil{fmt.Printf("recoverreceiveaerr:%+v\n",err)}}()panic("panicingoroutine.")}执行上面的代码,结果如下:可以看到,程序没有再崩溃了。那么一个新的问题出现了,能不能把recover()放在最外层的方法里,这样可以更好的实现recover()来覆盖当前方法下的所有panic。funcmain(){deferfunc(){err:=recover()iferr!=nil{fmt.Printf("recoverreceiveaerr:%+v\n",err)}}()gopanicInGoroutine()//下面3行代码是挂起控制台,等待gorouine运行完毕。fmt.Println("wait")input:=bufio.NewScanner(os.Stdin)input.Scan()}funcpanicInGoroutine(){panic("panicingoroutine.")}运行结果:还是崩溃了。如果你是习惯了try-catch-finally运行效果的Java或.Net程序员,一定会对这个结果感到惊讶。父方法中定义的recover()无法捕获子方法中的恐慌。其实这里的原因是外层方法中定义的recover()无法捕捉到goroutine执行的子方法中抛出的panic。上面代码中,我们去掉gopanicInGoroutine()前面的go就可以正常catch了。好了,那么根据以上信息,处理panic的正确姿势是什么呢?必须使用defer关键字来调用recover()。通过goroutine调用方法时,一定要保证里面有recover()机制。如果你想深入了解panic和recoverr的机制,给你分享一个很棒的硬核视频:https://www.bilibili.com/video/BV155411Y7XT,第一次看可能会有点头晕。建议反复观看。直到完全理解原理。