Defer, Panic, and Recover
来源:互联网 发布:股票下载什么软件 编辑:程序博客网 时间:2024/06/05 09:54
工作之后两年多没写过博客了,给自己找了一个忙的借口,快过年了,有点时间,最近学习go语言,习惯于学习官方文档,发现
https://blog.golang.org/defer-panic-and-recover
这篇文章没有翻译版本,可能会延长学习的时间成本,就翻译了一下,翻译的不妥请大家指正
Defer, Panic, and Recover
Go语言有着通用的流程控制机制:if, for, switch,goto。同样有在独立go程中运行代码的机制。我们这里讨论一个相对于前两者不那么常用的机制:defer, panic和recover。
defer表达式将函数调用压进一个线性表中(理解为堆栈)。在所有上层函数返回后(即当前层次调用的所有函数返回后,并且当前函数调用return),线性表中的调用开始执行。defer一般被用来简化需要进行一些清理操作的函数。
举个例子,我们来看一个执行文件内容拷贝操作的函数,即打开两个文件,并将其中一个文件的内容拷贝到另一个文件中
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } dst, err := os.Create(dstName) if err != nil { return } written, err = io.Copy(dst, src) dst.Close() src.Close() return}
这个函数可以执行,但是有个bug,如果os.Create调用失败,那么函数将会返回,但是不会close源文件。这个问题只要在第二个return语句之前补一个src.Close就可以修复。但是如果这种情况发生在一个复杂逻辑中,这一问题可能并没有这么容易被发现并修复。通过引入defer表达式,我们可以保证文件close总是被调用:
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close() dst, err := os.Create(dstName) if err != nil { return } defer dst.Close() return io.Copy(dst, src)}
Defer语句允许我们在刚刚打开文件的时候就考虑如何关闭文件。无论函数有多少return语句,只要保证这一点,文件就会被close。
defer语句的行为是直接并且可以预测的,有三个简单的原则:
1.defer语句一旦运行,那么被执行defer操作的函数的参数值就被确定,举个例子:
func a() { i := 0 defer fmt.Println(i) i++ return}
在这个例子中,变量i在Println被defer调用时候就被计算出来,因此在函数返回后,被defer的调用会打印0而不是1
2. 在外层函数返回后,被defer的函数按照后进先出(LIFO,因此我理解为堆栈)的顺序执行,比如下面函数最终打印“3210”:
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) }}
3. 被defer的函数可能读取并且赋值给正在返回函数的具备名称的返回值。
func c() (i int) { defer func() { i++ }() return 1}
在这个例子中, 一个defer的函数在外层函数返回后对返回值i执行自增操作,因此,这个函数返回后i的值为2.这有利于修复函数的错误返回值,稍后将会看到一个例子。
panic是一个结束正常的控制流程,并且启动panicking(不知道怎么翻)机制的内建方法。当函数F调用panic时,F的执行结束,所有F中被defer的函数开始执行,然后F返回到调用者。对于调用者而言,F接下来的行为像一个对panic的调用。进程持续退栈操作直到当前go程的多有方法返回,在这一点程序失败。panic可以通过直接引入panic状态开始。他们可以被运行时错误导致,例如数组的越界访问。
recover是一个恢复对panicking状态go程控制的内建方法。recover只有在被defer的函数或方法中才有效。在正常执行过程中,对recover的调用将会返回nil,不会有其他影响。如果当前go程正处于panicking状态,对recover的调用将会捕捉传入panic的值并且恢复正常执行。
下面是一个用来阐明panic和defer的例程:
package mainimport "fmt"func main() { f() fmt.Println("Returned normally from f.")}func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") g(0) fmt.Println("Returned normally from g.")}func g(i int) { if i > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", i)) } defer fmt.Println("Defer in g", i) fmt.Println("Printing in g", i) g(i + 1)}
函数g入参为int类型的i,如果i大于3则panic,否则使用i+1递归调用函数自身。函数f对一个调用了recover并且打印被恢复的值(如果部位nil的话)的函数进行的defer操作。再继续阅读之前,尝试描述程序可能的输出。
程序将会输出:
Calling g.Printing in g 0Printing in g 1Printing in g 2Printing in g 3Panicking!Defer in g 3Defer in g 2Defer in g 1Defer in g 0Recovered in f 4Returned normally from f.
如果我们从f中将被defer的函数移除,那么panic就没有被recover,在达到go程调用栈顶端之后,终止程序,输出会如下所示:
Calling g.Printing in g 0Printing in g 1Printing in g 2Printing in g 3Panicking!Defer in g 3Defer in g 2Defer in g 1Defer in g 0panic: 4 panic PC=0x2a9cd8[stack trace omitted]
对于实际使用的panic和recover的例子,需要查看go 标准库的json包。它使用一系列的递归和循环函数解析使用json编码的数据。在遇到畸形的json数据时,解析器调用panic释放栈空间直到最高一级的函数调用,这一函数调用从panic恢复并且返回一个合理的error 值(详见 decode.go中解码状态类型'error' 和'unmarshal' 方法)
go标准库的约定是,虽然包内部使用了panic,他的外部接口依然要返回的明确error值。
其他defer用法(除了早先提到的file.Close的例子)包括释放一个互斥量
mu.Lock()defer mu.Unlock()
打印页脚
printHeader()defer printFooter()
以及其他的更多使用。
综上所述,defer语句(无论是否和panic与recover一起使用)提供了一套不同寻常但是有力的流程控制机制。这一机制可以用来规范其他语言的一大批使用特殊结构完成的功能,可以尝试一下。
英文版原作者:Andrew Gerrand
0 0
- Defer, Panic, and Recover
- Defer, Panic, and Recover
- Golang---Defer, Panic, and Recover
- [GoBlog] Defer, Panic, and Recover
- Understanding Defer, Panic and Recover
- Go-defer,panic,recover
- GO defer panic recover
- golang中的defer panic recover
- 理解Defer、Panic和Recover
- Golang中的defer, panic, recover
- Go的异常处理 defer, panic, recover
- Go的异常处理 defer, panic, recover
- Go的异常处理 defer, panic, recover
- 【GoLang】panic defer recover 深入理解
- golang之defer,panic,recover用法
- Go的异常处理 defer, panic, recover
- golang 使用defer、panic、recover的问题
- Go panic, defer, recover 的异常处理
- KiCad设计PCB-14-CAN通信与USB通信接口电路设计
- Bshare分享的常见用法
- squid日志处理(天)
- Spark算子:RDD基本转换操作(5)–mapPartitions、mapPartitionsWithIndex
- Java多线程编程总结
- Defer, Panic, and Recover
- LogStash实践日志分析二:收集数据、入库、数据分析和kibana展示
- 快速幂
- CentOS下修复yum安装工具
- 海康摄像头 Stream Channels的写法
- 标准时间GMT|UTC以及NTP协议学习
- 算法基础之ADT树
- 如何在Linux中查找一个文件
- python:HTTP Error 505: HTTP Version Not Supported