内容简介:翻译自:Go有和语言一样常见的流程控制语句:if, for, switch, goto。同时也可以使用go语句实现在不同的goroutine中运行代码(并发)。不过,今天我们将来讨论一些少见的话题:defer、panic和recover。defer语句会将函数推入到一个列表中。同时,列表中的函数会在return语句执行后被调用。defer常常会被用来简化资源清理释放之类的操作。
翻译自: https://blog.golang.org/defer-panic-and-recover
Go有和语言一样常见的流程控制语句:if, for, switch, goto。同时也可以使用 go 语句实现在不同的goroutine中运行代码(并发)。不过,今天我们将来讨论一些少见的话题:defer、panic和recover。
Defer
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
时出现了失败的情况,那么该函数将会在没有关闭源文件的情况下立即返回。此问题可以很容易地通过在第二个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语句让我们在打开文件时便要思考文件的关闭,不必在意过多return语句,便可实现资源的正确释放。
Defer语句的行为是明确可知的,此处有三条简单的规则:
- Defer调用函数的参数即defer语句调用时的参数值
比如下面这个例子,打印出来的变量i的值即是运行到defer语句时的值。在a函数执行return后,Defer后的函数调用,即Println,将会打印出 "0"。
func a() { i := 0 defer fmt.Println(i) i++ return }
- Defer列表中的函数将会在return语句之后按照先进后出的次序来执行,即LIFO。
下面这个函数的执行结果是 "3210"
func b() { for i := 0; i < 4; i++ { defer fmt.Print(i) } }
- Defer后的函数还可以读取return返回值并改变其值。
在下面的例子中,defer的函数中对函数返回值进行了自增操作,最终函数c的最终返回值是2.
func c() (i int) { defer func() { i++ }() return 1 }
这使我们可以非常方便的修改异常的函数返回。
Panic
panic是go的内置函数,它可以终止程序的正常执行流程并发出panic(类似其他语言的exception)。比如当函数F调用 panic
,f的执行将被终止,然后defer的函数正常执行完后返回给调用者。对调用者而言,F的表现就像调用者直接调用了panic。这个流程会栈的调用次序不断向上抛出panic,直到返回到goroutine栈顶,此时,程序将会崩溃退出。panic可以通过直接调用 panic
产生。同时也可能由运行时的错误所产生,例如数组越界访问。
Recover
recover是go语言的内置函数,它的主要作用是可以从panic的重新夺回goroutine的控制权。Recover必须通过defer来运行。在正常的执行流程中,调用recover将会返回nil且没有什么其他的影响。但是如果当前的goroutine产生了panic,recover将会捕获到panic抛出的信息,同时恢复其正常的执行流程。
下面这个例子向我们展示了panic、defer和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接收参数i,如果i大于3就会产生panic,否则调用g(i+1)。而函数f通过defer匿名函数来执行recover并打印出捕获到的panic信息(如r不等于nil)。在阅读代码前,可尝试打印下程序输出。
输出如下:
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中的recover,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]
下面我们来看一个真实的案例,来自go标准库的 json包 。它先通过一系列的递归函数解析json数据。当遇到非法json时,解释器就会产生panic,直到上层调用从panic中重新recover执行流程,并据此返回适当错误(具体可以参看decode.go文件中的decodeState的error和unmarshal方法)。
在go的库中的常见用法是,即使在包内部使用panic,但外部API仍然需要以清晰的error来返回错误信息。
下面是defer其他的一些使用场景(除了前面列出的file.close案例),例如锁的释放:
mu.Lock() defer mu.Unlock()
打印页尾:
printHeader() defer printFooter() and more.
总的来说,defer为我们提供了一种异常强大的流程控制机制(不仅仅限于panic、recover场景)。而且通过其他一些特殊要求的结构,它可以模仿许多其他语言中的特性。来试试看吧!
作者:Andrew Gerrand
以上所述就是小编给大家介绍的《Go的Defer、Panic和Recover (翻译)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 基于 Laravel、Lumen 框架集成百度翻译、有道翻译、Google 翻译扩展包
- 腾讯发布人工智能辅助翻译 致敬人工翻译
- golang调用baidu翻译api实现自动翻译
- 监管机器翻译质量?且看阿里如何搭建翻译质量评估模型
- 机器翻译新突破:谷歌实现完全基于attention的翻译架构
- PendingIntent 是个啥?官方文档描述的很到位。我给翻译翻译
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。