Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24134 人已学习
课程目录
已完结 54 讲
0/4登录后,你可以任选4讲全文学习。
开篇词+学习路线 (3讲)
开篇词 | 跟着学,你也能成为Go语言高手
免费
预习篇 | 写给0基础入门的Go语言学习者
50 | 学习专栏的正确姿势
模块一:Go语言基础知识 (6讲)
01 | 工作区和GOPATH
02 | 命令源码文件
03 | 库源码文件
04 | 程序实体的那些事儿(上)
05 | 程序实体的那些事儿(中)
06 | 程序实体的那些事儿 (下)
模块二:Go语言进阶技术 (16讲)
07 | 数组和切片
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)
23 | 测试的基本规则和流程 (上)
24 | 测试的基本规则和流程(下)
25 | 更多的测试手法
26 | sync.Mutex与sync.RWMutex
27 | 条件变量sync.Cond (上)
28 | 条件变量sync.Cond (下)
29 | 原子操作(上)
30 | 原子操作(下)
31 | sync.WaitGroup和sync.Once
32 | context.Context类型
33 | 临时对象池sync.Pool
34 | 并发安全字典sync.Map (上)
35 | 并发安全字典sync.Map (下)
36 | unicode与字符编码
37 | strings包与字符串操作
38 | bytes包与字节串操作(上)
39 | bytes包与字节串操作(下)
40 | io包中的接口和工具 (上)
41 | io包中的接口和工具 (下)
42 | bufio包中的数据类型 (上)
43 | bufio包中的数据类型(下)
44 | 使用os包中的API (上)
45 | 使用os包中的API (下)
46 | 访问网络服务
47 | 基于HTTP协议的网络服务
48 | 程序性能分析基础(上)
49 | 程序性能分析基础(下)
尾声与思考题答案 (2讲)
尾声 | 愿你披荆斩棘,所向无敌
新年彩蛋 | 完整版思考题答案
Go语言核心36讲
登录|注册

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

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

知识扩展

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

这其实很简单,在调用panic函数时,把某个值作为参数传给该函数就可以了。由于panic函数的唯一一个参数是空接口(也就是interface{})类型的,所以从语法上讲,它可以接受任何类型的值。
但是,我们最好传入error类型的错误值,或者其他的可以被有效序列化的值。这里的“有效序列化”指的是,可以更易读地去表示形式转换。
还记得吗?对于fmt包下的各种打印函数来说,error类型值的Error方法与其他类型值的String方法是等价的,它们的唯一结果都是string类型的。
我们在通过占位符%s打印这些值的时候,它们的字符串表示形式分别都是这两种方法产出的。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(26)

  • 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
    2
    26
  • 云学
    defer其实是预调用,产生一个函数对象,压栈保存,函数退出时依次取出执行
    2018-10-10
    14
  • 小龙虾
    我感觉还是go的这种设计好用,它会强迫开发者区别对待错误和异常,并做出不同的处理。相比try{}catch,我在开发中经常看到开发者把大段大段的代码或者整个处理写到try{}中,这本身就是对try{}catch的乱用

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

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

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

    2019-04-22
    5
  • sket
    感觉还是try{}catch这种异常处理好用
    2018-12-27
    5
  • liangjf
    Go 语言会把它携带的defer函数及其参数值另行存储到一个队列中。

    这个队列与该defer语句所属的函数是对应的,并且,它是先进后出(FILO)的,相当于一个栈


    直接表达为 创建defer时“函数对象“压栈,panic触发时出栈调用 更容易理解吧
    2019-02-25
    3
  • honnkyou
    「延迟到什么时候呢?这要延迟到该语句所在的函数即将执行结束的那一刻,无论结束执行的原因是什么。」
    以该节课中代码为例的话是要吃到main函数快结束时执行是吗?执行defer函数。

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

    2019-05-14
    2
  • 疯琴
    试验了一下在 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
    1
  • 風華
    如果defer中引发panic,那么在该段defer函数之前,需要另外一个defer来捕获该panic,并且代码中最后一个panic会被抛弃,由defer中的panic来成为最后的异常返回。
    2018-12-17
    1
  • Bang
    请问下,按您上面说的,一个recover只能恢复所在的那个函数。那如果一个 函数中有一个goroutine函数 而这个goroutine函数触发了panic,那是只有他自己可以recover是吗,他的上级是无法recover内部的goroutine的painc是这样吗?
    2018-11-22
    1
  • Bang
    请问下,按您上面说的,一个recover只能恢复所在的那个函数。那如果一个 函数中有一个goroutine函数 而这个goroutine函数触发了panic,那是只有他自己可以recover是吗,他的上级是无法recover内部的goroutine的painc是这样吗?
    2018-11-22
    1
  • 独自逛荡
    defer func(){.....1}()
    defer func(){.....2}()
    defer func(){.....3}()
    panic("4")
    我在goland里面分别选择 gc 和 gccgo编译
    发现执行的顺序不同 一个是3421一个是 3214
    2018-10-24
    1
  • 博博
    老师,最近遇到一个defer的问题,想请教一下!
    defer在声明的时候会把函数和它的参数求值后拷贝一份先入栈,如果这个参数是值,那么在真正执行的时候不会改变,如果这个参数是引用,那在真正执行前如果改变了它的话,最终defer执行时的参数应该是改变后的!但是切片这种引用类型的参数,为什么没办法改变呢,而如果是一个指针的话就会被改变?
    希望能得到您的解答,谢谢了!!!

    作者回复: 切片的话,要看你是怎么变的。比如,由于追加元素等操作导致切片以及底层数组彻底换新,原切片就不变。这需要具体情况具体分析,你最好把代码贴上来。

    2019-11-06
  • 翼江亭赋
    iava世界里曾经try catch满天飞,现在还能看到不少这种代码,但逐渐大家认同了在去掉这种代码。

    因为大部分catch住异常以后只是打个log再重新throw,这个交给框架代码在最外层catch住以后统一处理即可。非框架代码极少需要处理异常。

    go世界里,err guard满天飞,但大部分的处理也是层层上传。但做不到不用,因为不像try那样去掉catch后会自动往上传递,不检查err的话就丢失了,所以这种代码去不掉。只能继续满天飞。

    底层实现其实都是setjmp,主要的区别之一我认为是go设计者认为java异常的性能代价大。

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

    2019-10-31
    1
  • 老师的demo非常经典 每一个看着都很带劲
    2019-07-27
  • Sky
    是否可以这样理解,defer语句就是在析构的时候执行的。
    2019-07-09
  • benying
    打卡,这篇文章的评论都很赞啊
    2019-06-05
  • 清静淡泊
    关于思考题,可以的。

    大的控制流是这样的:
    panic函数那一行执行完后马上结束当前函数;而defer函数被调用就是发生在当前函数结束后的立即。这些规则/顺序是不变的。

    所以说,如果panic函数放在defer函数调用链上(某一个defer函数体中),只要后续defer函数有recover函数并处理了这个panic,那么恐慌就被处理了;同理,如果后续defer函数没有recover函数被放置,那么在依次运行完defer函数链上的函数后,panic会继续向函数栈上一级抛(如果是main就直接终止进程了)。
    2019-05-20
  • 虢国技匠
    打卡
    2019-01-22
  • Bang
    接着刚刚的提问不好意思了,那我们能不能统一在一个的地方处理panic呢
    2018-11-22
收起评论
26
返回
顶部