22 | panic函数、recover函数以及defer语句(下)
22 | panic函数、recover函数以及defer语句(下)
讲述:黄洲君
时长10:25大小9.55M
知识扩展
问题 1:怎样让 panic 包含一个值,以及应该让它包含什么样的值?
问题 2:怎样施加应对 panic 的保护措施,从而避免程序崩溃?
问题 3:如果一个函数中有多条defer语句,那么那几个defer函数调用的执行顺序是怎样的?
总结
思考题
赞 20
提建议
精选留言(43)
- wesleydeng2019-04-09从语言设计上,不使用try-catch而是用defer-recover有什么优势?c++和java作为先驱都使用try-catch,也比较清晰,为什么go作为新语言却要发明一个这样的新语法?有何设计上的考量?
作者回复: 这是两种完全不同的异常处理机制。Go语言的异常处理机制是两层的,defer和recover可以处理意外的的异常,而error接口及相关体系处理可预期的异常。Go语言把不同种类的异常完全区别对待,我觉得这是一个进步。 另外,defer机制能够处理的远不止异常,还有很多资源回收的任务可以用到它。defer机制和goroutine机制一样,是一种很有效果的创新。 我认为defer机制正是建立在goroutine机制之上的。因为每个函数都有可能成为go函数,所以必须要把异常处理做到函数级别。可以看到,defer机制和error机制都是以函数为边界的。前者在函数级别上阻止会导致非正常控制流的意外异常外溢,而后者在函数级别上用正常的控制流向外传递可预期异常。 不要说什么先驱,什么旧例,世界在进步,技术更是在猛进。不要把思维固化在某门或某些编程语言上。每种能够流行起来的语言都会有自己独有的、已经验证的语法、风格和哲学。
共 9 条评论89 - 云学2018-10-10defer其实是预调用,产生一个函数对象,压栈保存,函数退出时依次取出执行41
- 凌惜沫2019-04-22如果defer中引发panic,那么在该段defer函数之前,需要另外一个defer来捕获该panic,并且代码中最后一个panic会被抛弃,由defer中的panic来成为最后的异常返回。
作者回复: 嗯,是的,由于之前发生的 panic 已经被 recover 了,所以最终被抛出去的就应该是外层 defer 语句中的那个 panic。
共 2 条评论18 - 小龙虾2019-04-23我感觉还是go的这种设计好用,它会强迫开发者区别对待错误和异常,并做出不同的处理。相比try{}catch,我在开发中经常看到开发者把大段大段的代码或者整个处理写到try{}中,这本身就是对try{}catch的乱用
作者回复: 是的,这是最主要好处。
共 2 条评论16 - sket2018-12-27感觉还是try{}catch这种异常处理好用共 1 条评论8
- 颇忒妥2020-02-05作者想把概念给我们讲清楚,但是我总觉着看着费劲。为啥?因为作者太啰嗦了。比如: defer函数调用的执行顺序与它们分别所属的defer语句的出现顺序(更严谨地说,是执行顺序)完全相反。 改成这样就简单多了:defer函数的调用顺序与其defer语句执行顺序相反。 还有:当一个函数即将结束执行时,其中的写在最下边的defer函数调用会最先执行,其次是写在它上边、与它的距离最近的那个defer函数调用,以此类推,最上边的defer函数调用会最后一个执行。 改成:当一个函数即将执行结束时,最下面的defer函数先执行,然后是倒数第二个,以此类推。展开共 3 条评论7
- liangjf2019-02-25Go 语言会把它携带的defer函数及其参数值另行存储到一个队列中。 这个队列与该defer语句所属的函数是对应的,并且,它是先进后出(FILO)的,相当于一个栈 直接表达为 创建defer时“函数对象“压栈,panic触发时出栈调用 更容易理解吧展开6
- 名:海东2020-07-08//测试场景1 func Test() { defer func() { if errRecover := recover(); errRecover != nil { fmt.Println("recover2...") } fmt.Println("no recover2...") }() defer func() { test01() // test01()方法在defer func(){}中执行 }() b := 0 a := 1 / b fmt.Println(a) return } func test01() { if e := recover(); e != nil { fmt.Println("recover...") } else { fmt.Println("no recover...") } fmt.Println("defer exe...") } func main() { Test() } //输出: no recover... defer exe... recover2... no recover2... //测试场景2 func Test() { defer func() { if errRecover := recover(); errRecover != nil { fmt.Println("recover2...") } fmt.Println("no recover2...") }() defer test01() //test01()直接放到defer后面 b := 0 a := 1 / b fmt.Println(a) return } func test01() { if e := recover(); e != nil { fmt.Println("recover...") } else { fmt.Println("no recover...") } fmt.Println("defer exe...") } func main() { Test() } //输出: recover... defer exe... no recover2... 我的问题是:为什么场景1中出现panic没有在defer func() { test01() }()中被recover,而在defer func() { if errRecover := recover(); errRecover != nil { fmt.Println("recover2...") } fmt.Println("no recover2...") }()中被recover。 场景2使用defer test01 的写法后就可以被recover。展开
作者回复: 很简单,在场景一中,test01 函数不是一个 defer 函数(它只是被 defer 函数调用了而已);而在场景二中,test01 函数却是一个不折不扣的 defer 函数。只有直接在 defer 函数中调用 recover() 函数才能起到恢复 panic 的作用。
共 3 条评论4 - 疯琴2019-12-07试验了一下在 goroutine 里面 panic,其他的 goroutine(比如main)是 recover()不到的: func main() { fmt.Println("start") defer func() { if p := recover(); p != nil { fmt.Println(p) } }() var wg sync.WaitGroup wg.Add(1) go func() { defer func() { wg.Done() }() panic(errors.New("panic in goroutine")) }() wg.Wait() }展开
作者回复: 当然。因为它们之间不是串行的关系,所以 panic 传播不到其他的 goroutine 那里。所以,每个 goroutine 都应该有自己的异常处理代码。我们可以设计一个整体的异常处理规则或体系,并在每个 goroutine 里都遵循它。
4 - 翼江亭赋2019-10-31iava世界里曾经try catch满天飞,现在还能看到不少这种代码,但逐渐大家认同了在去掉这种代码。 因为大部分catch住异常以后只是打个log再重新throw,这个交给框架代码在最外层catch住以后统一处理即可。非框架代码极少需要处理异常。 go世界里,err guard满天飞,但大部分的处理也是层层上传。但做不到不用,因为不像try那样去掉catch后会自动往上传递,不检查err的话就丢失了,所以这种代码去不掉。只能继续满天飞。 底层实现其实都是setjmp,主要的区别之一我认为是go设计者认为java异常的性能代价大。展开
作者回复: Go 的 error 其实就是在用普通的控制流来处理异常。但是性能却可以有非常明显的提高。其实不管怎么弄都做不到“羊毛出在猪身上”。不管是让开发者自行处理,还是运行时系统自己控制,都会对程序的流畅度产生影响。这就是程序稳定性和程序流畅度(包括可读性、控制流和性能等)之间的trade off。
共 3 条评论3 - 風華2018-12-17如果defer中引发panic,那么在该段defer函数之前,需要另外一个defer来捕获该panic,并且代码中最后一个panic会被抛弃,由defer中的panic来成为最后的异常返回。3
- 来碗绿豆汤2018-10-01可以 defer 有点类似java中的final语句,里面还可以抛出异常。这样的好处是,我们捕获panic之后,可以对起内容进行查看,如果不是我们关注的panic那么可以继续抛出去
作者回复: 对的。不过还是有不少不一样的地方的,可以体会一下。
3 - Zz~2021-01-02老师,您好,我想问一下,如果在main函数里调用一个我自定义的panic方法,recover可以恢复;但是如果我将自定义的panic方法改为go mypanic这样,recover就不能恢复。这是什么原因呢?下面是我实验的代码 ==============可以恢复的============== package main import ( "errors" "fmt" ) func myRecover() { if err := recover(); err != nil { fmt.Printf("panic is %s", err) } } func myPanic() { panic(errors.New("自定义异常")) } func main() { defer myRecover() myPanic() } =================不可以恢复的============== package main import ( "errors" "fmt" "time" ) func myRecover() { if err := recover(); err != nil { fmt.Printf("panic is %s", err) } } func myPanic() { panic(errors.New("自定义异常")) } func main() { defer myRecover() go myPanic() time.Sleep(time.Second * 5) }展开
作者回复: 某个goroutine中的panic是不可能由别的goroutine中的recover恢复的。或者说,一个goroutine中的panic只能由自己例程中的recover恢复。
共 2 条评论2 - honnkyou2019-05-14「延迟到什么时候呢?这要延迟到该语句所在的函数即将执行结束的那一刻,无论结束执行的原因是什么。」 以该节课中代码为例的话是要吃到main函数快结束时执行是吗?执行defer函数。
作者回复: defer 函数的执行时刻是在直接包含它的那个函数即将执行完毕的时候,也可以理解为下一刻就要返回结果值(如果有的话)的时候。对于 main 函数直接包含的 defer 函数来说,也是如此。
共 2 条评论2 - Bang2018-11-22请问下,按您上面说的,一个recover只能恢复所在的那个函数。那如果一个 函数中有一个goroutine函数 而这个goroutine函数触发了panic,那是只有他自己可以recover是吗,他的上级是无法recover内部的goroutine的painc是这样吗?共 1 条评论2
- 有匪君子2018-10-01这个问题就引发了另一个问题。defer可以在同一个函数中嵌套使用吗?感觉这两个问题答案应该一致
作者回复: 只要是函数就都可以。
2 - 传说中的成大大2020-03-26今日总结 本章主要是讲了panic和recover以及defer panic主要是用来抛出恐慌 而recover主要用来恢复恐慌,但是recover使用即恢复 如果在此时没有恐慌产生就会返回一个nil 也正是因为recover使用即恢复的特性 所以要把recover执行在产生恐慌之后,但是panic之后的代码不会再执行所以引入defer表达式 defer表达式 主要是用来代码延迟执行 延迟到函数结束,所以一般把recover跟defer搭配 defer的执行 没执行一次defer语句产生一个defer函数 并且将其放入一个额外的栈中 所以是个先入后出的顺序!所以defer 函数的执行顺序与其声明顺序完全相反 关于思考题: 我觉得从理论上来说 我们没必要完全人为的引起panic 但是如果是不小心引起的panic那也无法避免,同时通过测试也是可以引发panic的展开1
- 张sir2020-03-12您好,请问defer函数压𣏾的时候,为什么把当时的入参也放入𣏾中呢 ``` func test() { var a, b = 1, 1 defer func(flag int) { fmt.Println(flag) }(a + b) a = 2 b = 2 } ``` 这个输出的2不是4的理论论据是什么呢展开
作者回复: 参数值flag是2,因为Go会在压栈前先求出给予defer函数的那些参数值。
1 - Pana2020-03-05在defer 中 recover 了panic ,是否还能让函数返回错误呢
作者回复: 可以,函数需要有一个有名称的error类型的结果,然后在该函数内recover之后为这个结果赋值。这样就可以了。
1 - Bang2018-11-22请问下,按您上面说的,一个recover只能恢复所在的那个函数。那如果一个 函数中有一个goroutine函数 而这个goroutine函数触发了panic,那是只有他自己可以recover是吗,他的上级是无法recover内部的goroutine的painc是这样吗?1