Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
79610 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

22 | panic函数、recover函数以及defer语句(下)

用于更易读地表示形式转换
error类型值的Error方法与其他类型值的String方法等价
可更易读地表示形式转换
需要关注多个defer函数调用的实际执行顺序
同一条defer语句每被执行一次,就会产生一个延迟执行的defer函数调用
与所属的defer语句的执行顺序完全相反
会延迟到该defer语句所属的函数即将结束执行的那一刻
会让其携带的defer函数的调用延迟执行
只有在defer语句中才能起作用
如果是通过panic函数引发的panic,返回传入参数值的副本
如果没有panic发生,返回nil
为值关联String方法
其他有效序列化的值
error类型的错误值
有意引发panic时可自行指定值
由Go语言运行时系统给定
可以在defer函数中引发panic
联用for语句
延迟执行的defer函数调用顺序
被用来延迟执行代码
用于延迟执行代码
联用defer语句
返回空接口类型的值
用于恢复panic
应包含的值
可包含值
思考题
defer语句
recover函数
panic函数
Go语言异常处理

该思维导图由 AI 生成,仅供参考

你好,我是郝林,今天我们继续来聊聊 panic 函数、recover 函数以及 defer 语句的内容。
我在前一篇文章提到过这样一个说法,panic 之中可以包含一个值,用于简要解释引发此 panic 的原因。
如果一个 panic 是我们在无意间引发的,那么其中的值只能由 Go 语言运行时系统给定。但是,当我们使用panic函数有意地引发一个 panic 的时候,却可以自行指定其包含的值。我们今天的第一个问题就是针对后一种情况提出的。

知识扩展

问题 1:怎样让 panic 包含一个值,以及应该让它包含什么样的值?

这其实很简单,在调用panic函数时,把某个值作为参数传给该函数就可以了。由于panic函数的唯一一个参数是空接口(也就是interface{})类型的,所以从语法上讲,它可以接受任何类型的值。
但是,我们最好传入error类型的错误值,或者其他的可以被有效序列化的值。这里的“有效序列化”指的是,可以更易读地去表示形式转换。
还记得吗?对于fmt包下的各种打印函数来说,error类型值的Error方法与其他类型值的String方法是等价的,它们的唯一结果都是string类型的。
我们在通过占位符%s打印这些值的时候,它们的字符串表示形式分别都是这两种方法产出的。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

在Go语言中,程序异常的处理是非常重要的,本文深入介绍了panic、recover和defer的相关知识。作者首先强调了在程序异常时记录相关信息的重要性,并指出了为值关联String方法的必要性。其次,作者介绍了如何施加应对panic的保护措施,避免程序崩溃,重点讲解了recover函数的正确用法和与defer语句的联用。最后,作者提到了关于defer语句的一些内容,包括在同一个函数中,defer函数调用的执行顺序与它们分别所属的defer语句的出现顺序完全相反。总的来说,本文内容涵盖了Go语言中特殊的程序异常及其处理方式的核心知识,对于读者快速了解这些内容具有很好的指导意义。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(45)

  • 最新
  • 精选
  • wesleydeng
    从语言设计上,不使用try-catch而是用defer-recover有什么优势?c++和java作为先驱都使用try-catch,也比较清晰,为什么go作为新语言却要发明一个这样的新语法?有何设计上的考量?

    作者回复: 这是两种完全不同的异常处理机制。Go语言的异常处理机制是两层的,defer和recover可以处理意外的的异常,而error接口及相关体系处理可预期的异常。Go语言把不同种类的异常完全区别对待,我觉得这是一个进步。 另外,defer机制能够处理的远不止异常,还有很多资源回收的任务可以用到它。defer机制和goroutine机制一样,是一种很有效果的创新。 我认为defer机制正是建立在goroutine机制之上的。因为每个函数都有可能成为go函数,所以必须要把异常处理做到函数级别。可以看到,defer机制和error机制都是以函数为边界的。前者在函数级别上阻止会导致非正常控制流的意外异常外溢,而后者在函数级别上用正常的控制流向外传递可预期异常。 不要说什么先驱,什么旧例,世界在进步,技术更是在猛进。不要把思维固化在某门或某些编程语言上。每种能够流行起来的语言都会有自己独有的、已经验证的语法、风格和哲学。

    2019-04-09
    9
    94
  • 凌惜沫
    如果defer中引发panic,那么在该段defer函数之前,需要另外一个defer来捕获该panic,并且代码中最后一个panic会被抛弃,由defer中的panic来成为最后的异常返回。

    作者回复: 嗯,是的,由于之前发生的 panic 已经被 recover 了,所以最终被抛出去的就应该是外层 defer 语句中的那个 panic。

    2019-04-22
    2
    22
  • 小龙虾
    我感觉还是go的这种设计好用,它会强迫开发者区别对待错误和异常,并做出不同的处理。相比try{}catch,我在开发中经常看到开发者把大段大段的代码或者整个处理写到try{}中,这本身就是对try{}catch的乱用

    作者回复: 是的,这是最主要好处。

    2019-04-23
    2
    17
  • 名:海东
    //测试场景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 的作用。

    2020-07-08
    3
    4
  • 疯琴
    试验了一下在 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 里都遵循它。

    2019-12-07
    4
  • 翼江亭赋
    iava世界里曾经try catch满天飞,现在还能看到不少这种代码,但逐渐大家认同了在去掉这种代码。 因为大部分catch住异常以后只是打个log再重新throw,这个交给框架代码在最外层catch住以后统一处理即可。非框架代码极少需要处理异常。 go世界里,err guard满天飞,但大部分的处理也是层层上传。但做不到不用,因为不像try那样去掉catch后会自动往上传递,不检查err的话就丢失了,所以这种代码去不掉。只能继续满天飞。 底层实现其实都是setjmp,主要的区别之一我认为是go设计者认为java异常的性能代价大。

    作者回复: Go 的 error 其实就是在用普通的控制流来处理异常。但是性能却可以有非常明显的提高。其实不管怎么弄都做不到“羊毛出在猪身上”。不管是让开发者自行处理,还是运行时系统自己控制,都会对程序的流畅度产生影响。这就是程序稳定性和程序流畅度(包括可读性、控制流和性能等)之间的trade off。

    2019-10-31
    3
    3
  • 来碗绿豆汤
    可以 defer 有点类似java中的final语句,里面还可以抛出异常。这样的好处是,我们捕获panic之后,可以对起内容进行查看,如果不是我们关注的panic那么可以继续抛出去

    作者回复: 对的。不过还是有不少不一样的地方的,可以体会一下。

    2018-10-01
    3
  • Zz~
    老师,您好,我想问一下,如果在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恢复。

    2021-01-02
    2
    2
  • honnkyou
    「延迟到什么时候呢?这要延迟到该语句所在的函数即将执行结束的那一刻,无论结束执行的原因是什么。」 以该节课中代码为例的话是要吃到main函数快结束时执行是吗?执行defer函数。

    作者回复: defer 函数的执行时刻是在直接包含它的那个函数即将执行完毕的时候,也可以理解为下一刻就要返回结果值(如果有的话)的时候。对于 main 函数直接包含的 defer 函数来说,也是如此。

    2019-05-14
    2
    2
  • 有匪君子
    这个问题就引发了另一个问题。defer可以在同一个函数中嵌套使用吗?感觉这两个问题答案应该一致

    作者回复: 只要是函数就都可以。

    2018-10-01
    2
收起评论
显示
设置
留言
45
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部