Defer,Panic,and Recover
作者 | 陌无崖
转载请联系授权
Defer,Panic,and Recover
Andrew Gerrand 4 August 2010
Go拥有一般的控制流程机制,像if、for、switch、goto。除此之外go也拥有一个单独的goroutine机制运行go语句。这里我想讨论一些不太常见的语法:defer,panic,and recover
defer语句将函数调用推送到列表上,这个保存的列表会在周围的函数执行之后才开始执行,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()失败,这个函数会返回一个没有关闭的文件资源。在第二个return语句调用之前放置一个src.Close()可以轻松的解决这个问题。但是通过引入defer语句,我们可以确保我们的文件总是关闭的:
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语句使我们可以考虑正确关闭每一个打开的文件,从而保证无论函数的返回语句的数量如何,文件都会被关闭。
Defer语句的行为是直观的和可预测的.这有三个简单的规则:
1. 当对defer语句进行评价(使用)时,将对延迟函数的参数进行求值
在这个例子中,当Println()函数被延迟执行的时候,i运算式被使用,延迟调用将在函数返回后打印“0”。
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
2. 当周围的函数返回后,defer函数按照后进先出的顺序进行调用。
这个函数输出 "3210"
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}
3. defer函数可以读取和分配给返回函数的命名返回值
这个例子中,defer函数在周围的函数执行后递增返回i,因此这个函数返回2
func c() (i int) {
defer func() { i++ }()
return 1
}
这对于修改错误返回值很方便,我们将很快看到这样的一个例子。
Panic是一个内置的函数,它可以停止常规控制流并开始panic,F函数调用了panic时,F的执行会被停止,F中的任何defer函数正常执行,然后F返回给它的调用者,对于调用者,F的行为是一个panic的调用,该过程将会继续向上进行堆栈直到返回当前的goroutine中的所有函数都返回,此时程序崩溃,panic可以直接通过引用panic来引发panic,它们也可以在程序运行错误的时候导致,比如越界数组的访问。
Recover是一个内置函数,它可以重新获取正在panic线程的控制。恢复仅仅在defer函数内部有用。当正常执行期间,recover会返回nil并且没有其它的效果。如果当前的goroutine正在panic,recover将会给panic一个值使其恢复正常执行。
这有一个panic和recover的例子,演示了这种机制:
package main
import "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 defer会被调用reecover并打印恢复值(如果非零)的函数。再继续阅读之前,请尝试描绘出该程序的输出内容。
这个程序会输出
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.
如果从f中移除了这个defer函数,这个panic将不会被恢复并且将直接到达goroutine调用堆栈的帝国不,从而终止了程序,这个修改后的程序将会输出:
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
panic: 4
panic PC=0x2a9cd8
[stack trace omitted]
有关panic和recover的实际示例,请参见Go标准库中的 json package它使用了一组递归函数对接口进行编码,如果遍历该值的时候发生了错误,则会调用panic将堆栈展开到顶级函数调用,该调用从panic中恢复并且返回适当的错误值(请参阅encode.go中encodeState类型的error和marshal方法)
在Go库中的约定甚至是当一个内部包使用了panic,它外部的API仍然会显示的显示出错误值。
defer的其他用法(在文件之外。前面给出的关闭示例)包括释放互斥量
mu.Lock()
defer mu.Unlock()
打印尾部内容
printHeader()
defer printFooter()
总而言之,defer语句(带有或不带有panic和recovery)提供了一种异常强大的控制流机制。它可以用来建模由其他编程语言中的专用结构实现的许多功能。试试看。
本文为Golang官方博客部分文章的外文翻译,官方案例更加有料哦
- 用Java实现处理日期的工具类——常用日期处理方法
- ORM查询语言(OQL)简介--实例篇
- 漏洞预警 | 海洋CMS(SEACMS)0day漏洞预警
- 【机器学习】分类算法评价
- Java中图片处理工具类——能满足各种需求
- onclicklistener到底怎么用?
- 如何入侵联网智能灯泡——LIFX智能灯泡
- Java实现的一个编号生成器工具类——5种方法
- 【机器学习】有趣的机器学习:最简明入门指南
- 不使用反射,“一行代码”实现Web、WinForm窗体表单数据的填充、收集、清除,和到数据库的CRUD
- 海量数据处理利器之布隆过滤器
- ORM查询语言(OQL)简介--概念篇
- Discuz! 任意文件删除漏洞重现及分析
- .NET DLR 上的IronScheme 语言互操作&&IronScheme控制台输入中文的问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Dynamic Programming - 279. Perfect Squares
- Dynamic Programming - 120. Triangle
- Dynamic Programming - 63. Unique Paths II
- Tree - 109. Convert Sorted List to Binary Search Tree
- Tree - 108. Convert Sorted Array to Binary Search Tree Easy
- Tree - 236. Lowest Common Ancestor of a Binary Tree
- Tree - 235. Lowest Common Ancestor of a Binary Search Tree
- Tree - 98. Validate Binary Search Tree
- Tree - 199. Binary Tree Right Side View
- Tree - 103. Binary Tree Zigzag Level Order Traversal
- Tree - 107. Binary Tree Level Order Traversal II
- Trie - 212. Word Search II
- Trie - 211. Add and Search Word - Data structure design
- Trie - 208. Implement Trie (Prefix Tree)
- Tree - 337. House Robber III