21 | panic函数、recover函数以及defer语句 (上)
21 | panic函数、recover函数以及defer语句 (上)
讲述:黄洲君
时长07:45大小3.55M
前导知识:运行时恐慌 panic
问题解析
总结
思考题
赞 18
提建议
精选留言(15)
- 江山如画2018-10-08一个函数如果要把 panic 转化为error类型值,并将其结果返回给调用方,可以考虑把 defer 语句封装到一个匿名函数之中,下面是实验的一个例子,所用函数是一个除法函数,当除数为0的时候会抛出 panic并捕获。 func divide(a, b int) (res int, err error) { func() { defer func() { if rec := recover(); rec != nil { err = fmt.Errorf("%s", rec) } }() res = a / b }() return } func main() { res, err := divide(1, 0) fmt.Println(res, err) // 0 runtime error: integer divide by zero res, err = divide(2, 1) fmt.Println(res, err) // 2 <nil> }展开共 2 条评论37
- 锋2020-03-06老师,你好,我有一个疑问,请教一下,谢谢~ Go在设计的时候没有设计try...catch...finally这样的方式来捕获异常。 我在网上查很多人用panic、defer和recover组合来实现异常的捕获,甚至很多都将这个二次封装之后作为一个库来进行使用。 我的疑问是,从Go的设计角度为什么要这么做?是出于什么样的目的,还是他俩之间有什么优劣? 非常感谢~,烦请解答。展开
作者回复: Go的错误处理机制是由两个部分组成的,panic代表着特殊的(或者说意外的)错误,error代表着普通的错误。与try-catch不同,error并不是打断正常的控制流程的执行。单单这一点来讲,就已经是非常好的进步了。相比之下,panic会打断正常的控制流程。从这一点上看,panic与try-catch很像。 说到这里,你可能也意识到了,try-catch是一套行为单一的错误处理机制,而Go语言的(error+panic)把错误处理机制在代码级别分为了两个部分。 这样的好处是,倒逼开发者去思考,什么时候应该返回普通的错误,什么时候应该抛出意外的错误。这种思考在设计一个程序的错误体系的时候是非常重要的,关系到程序运行的稳定性。 至于缺点,error容易被滥用,导致程序中到处是 if err != nil 的代码。但是我们要清楚的是,这往往是程序设计上的问题,而不是语言层面的问题。如果不当心,try-catch照样会被弄的满屏都是。而且try-catch还有一个颗粒度和数量的问题(与临界区的颗粒度和数量问题类似)。 总之,我个人认为Go语言的错误处理机制是一种创新和进步。不过,由于容易被滥用,Go语言团队不是还在近几年一直在考虑更好的解决方案吗。我也很期待他们新的设计。
共 8 条评论17 - Bang2018-09-28先使用go中的类似try catch这样的语句,将异常捕获的异常转为相应的错误error就可以了13
- 唐丹2018-09-28郝大,你好,我在golang 8中通过recover处理panic时发现,必须在引发panic的当前协程就处理掉,否则待其传递到父协程直至main方法中,都不能通过recover成功处理掉了,程序会因此结束。请问这样设计的原因是什么?那么协程是通过panic中记录的协程id来区分是不是在当前协程引发的panic的吗?另外,这样的话,我们应用程序中每一个通过go新起的协程都应该在开始的地方recover,否则即使父协程有recover也不能阻止程序因为一个意外的panic而挂掉?盼望解答,谢谢🙏展开
作者回复: 只要在调用栈路径上就都可以处理,如果你用了defer语句和recover函数等正确处理方式还是不行的话,就要看看这个panic是不是不了恢复的。一些runtime抛出来的panic是不可恢复的,因为问题很严重必须整改代码才行。
共 5 条评论11 - 沐夜星光2020-05-21“控制权如此一级一级地沿着调用栈的反方向传播至顶端,也就是我们编写的最外层函数那里”。最外层函数是go函数,也就说当panic触发,通过其所在的goroutine,将控制权转移给运行时系统时,是不一定经过main函数的吗?另外老师能不能讲讲,go是怎么回收一个进程的,怎么处理运行中的goroutine以及涉及的资源。
作者回复: 这会经过main函数,异步调用也是调用。main函数就是主goroutine的go函数。 go回收进程?这没什么稀奇的啊,就是调用操作系统的底层API,你可以查看 runtime.main 函数了解相关过程,或者查看 runtime.exit 函数了解进程退出时调用的API。 在一个goroutine中的go函数执行完之后,这个goroutine会转为_Gidle状态,其中的所有关键字段都会被重置,它的栈内存也会被释放。最后,它会被放入自由G列表。你可以通过查看 runtime.goexit0 了解到。
共 2 条评论4 - 冰激凌的眼泪2018-10-02panic时,会捕获异常及异常上下文(函数名+文件行) 类似看作有一个异常上下文列表,始于异常触发处,沿着函数调用逆向展开,每一级append自己的异常上下文,直至goroutine入口函数,最终被runtime捕获 最终异常信息被打印,异常上下文列表被顺序打印,程序退出3
- 🐻2019-03-03https://gist.github.com/bwangelme/9ce1c606ba9f69c72f82722adf1402e12
- A 凡2018-10-24之前自己学习时候的一些模糊点更加清晰了,支持!2
- MClink2021-03-25初学go都会吐槽说没有 try catch , 应该不止我一个
作者回复: 每种语言的风格都不同啊,习惯习惯就好了(注意力可以更多的放在“怎样高效构建优秀软件上” ,语法的一些特点不属于关键问题 :-) ),而且后续Go在这方面会有改善。
共 4 条评论1 - MClink2022-07-07这个看起来比较简单 func main() { res, err := divide(1, 0) if err != nil { fmt.Println(err) } fmt.Println(res) } func divide(a, b int) (res int, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("omg, panic ! err:%v", r) return } }() res = a / b return }展开
- Harlan2021-08-12go这种满屏幕都是 判断 err!=nil 这种没有意义的代码 代码侵入性极强 也不优雅
- Geek_37a4412020-12-15老师,你好,我想问下,panic触发的时机,比如指令执行过程中,在什么时候会调用到相关的panic,比如数组越界是什么时候调用runtime.panicIndex,是有个额外的线程不断检测有异常了吗?
作者回复: 当然不是了,是执行程序的程序碰到严重错误就抛出来,属于Go运行时系统的职责范围。
共 4 条评论 - 传说中的成大大2020-03-25今日总结 今天主要是讲了panic运行时恐慌,一般发生在运行时 同样也可以自己调用内置的panic来主动引起恐慌 一般用来自己调试程序异常等等 主要掌握panic的执行过程 首先从某一行引起了panic 然后返回到其调用函数 这样一级一级的返回 直到最顶层也即main函数中 最后把控制权交给了go运行时状态,最后程序奔溃 进程退出 panic的信息一般也在这个返回过程中不断的完善 panic: runtime error: index out of range //错误信息 //以下是调用堆栈 goroutine 1 [running]: main.caller2() /home/ubuntu/geek_go/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:22 +0x91 main.caller1() /home/ubuntu/geek_go/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:15 +0x66 main.main() /home/ubuntu/geek_go/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:9 +0x66 exit status 2 关于思考题 我也先想了一下 我们捕获异常并将错误信息封装到一个error当中最后返回这个err 后来又去翻了解答 好像就是这个 样子的展开
- 虢國技醬2019-01-21打卡
- melody_future2018-12-25panic、recover 有点像try、、catch。这样应该会好理解很多