写点什么

[翻译]Defer,Panic,and Recover

用户头像
卓丁
关注
发布于: 2020 年 09 月 05 日
[翻译]Defer,Panic,and Recover

翻译的意义在于:如何更好的成就读者~

The Go Blog


Defer, Panic, and Recover

Andrew Gerrand

4 August 2010 [2010 年 8 月 4 号]

Go has the usual mechanisms for control flow: if, for, switch, goto. It also has the go statement to run code in a separate goroutine. Here I'd like to discuss some of the less common ones: defer, panic, and recover.

Go 语言和其他语言类似,具有传统的流程控制机制,比如使用if,for,switch,goto等关键字来实现条件判断、循环、选择,跳转等;额外,Go语言也从语言层面支持独立执行的goroutinue,使用go关键字就可以很方便的使用;下面我们主要想讨论一些Go的其他比较独特的特性defer、panic以及recover等。
复制代码


defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.

For example, let's look at a function that opens two files and copies the contents of one file to the other:

在Go语言中,我们可以使用defer语句来实现将某个调用放入一个暂存列表中,待延迟执行;然后当所在主函数执行结束返回时,会触发之前用defer定义对延迟调用;利用这一特性,我们就可以很容易的实现一些在主函数结束后,需要执行的一些清理工作相关的功能;(译者举例:比如资源释放,句柄关闭之类的)如下举例:打开来2个文件,实现将其中一个文件的内容拷贝到另外一个文件的功能;「译者注:流程:打开源文件和目标文件、调用Copy将源文件内容拷贝到目标文件、关闭刚打开过的两文件描述符」
复制代码


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}
复制代码

This works, but there is a bug. If the call to os.Create fails, the function will return without closing the source file. This can be easily remedied by putting a call to src.Close before the second return statement, but if the function were more complex the problem might not be so easily noticed and resolved. By introducing defer statements we can ensure that the files are always closed:

上述的代码可以执行,但还是存在问题的;试想,在调用os.Create创建目标文件的句柄时,如果失败了,那么CopyFile将会直接返回,这样就会导致咱们对源文件的打开位未关闭;如果我们在第二个return返回之前调用src.Close将源文件句柄关闭,则可以避免刚才的问题;但实际的场景中,如果整个函数的执行流程和调用链比较复杂的话,那可能很容易将此类Close遗忘或者不容易被发现;Go通过引入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 statements allow us to think about closing each file right after opening it, guaranteeing that, regardless of the number of return statements in the function, the files will be closed.

The behavior of defer statements is straightforward and predictable. There are three simple rules:

Defer语句有助于我们将打开后的每个文件及时关闭,这样以来,可以确保文件最终都将被关闭,而不受主体函数中return语句数量的影响;Defer语句的特性直接明了并且容易预测。其中有3个简单的规则:
复制代码
  1. A deferred function's arguments are evaluated when the defer statement is evaluated.

In this example, the expression "i" is evaluated when the Println call is deferred. The deferred call will print "0" after the function returns.

当分析defer语句的执行结果时,主要可以分析被defer语句定义的函数的参数;如下面的例子中:当Println函数被defer语句延迟执行时,主要分析变量i ,当宿主函数a()结束执行并返回时,延迟调用最终打印输出 "0" 
复制代码


func a() {    i := 0    defer fmt.Println(i)    i++    return}
复制代码
  1. Deferred function calls are executed in Last In First Out order after the surrounding function returns.

This function prints "3210":

当宿主函数b()返回时,Defer延迟调用将按照后进先出的顺序执行调用体。如下代码,将打印"3210"
复制代码


func b() {    for i := 0; i < 4; i++ {        defer fmt.Print(i)    }}
复制代码
  1. Deferred functions may read and assign to the returning function's named return values.

In this example, a deferred function increments the return value i after the surrounding function returns. Thus, this function returns 2:

此段略过,暂未译;原文是否正确,待考证~ 
复制代码


func c() (i int) {    defer func() { i++ }()    return 1}
复制代码

This is convenient for modifying the error return value of a function; we will see an example of this shortly.

利用defer这一特性,我们可以很方便但修改函数但错误返回值。👇下面我们马上会举类似的例子。
复制代码

Panic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses.

Panic函数是Go语言中的一个内置函数,它会终止程序的正常执行流程,并产生一个异常。比如有函数F的调用产生panic异常的话,那么F的执行会马上停止,并正常执行F函数体中定义的defer 调用(如果的话),然后F函数会返回到调用它的上一层调用者。对应F函数的调用者而言F函数这时就会表现出调用时发生的异常。当前进程将会继续执行相关的调用堆栈直至所有相关函数结束并返回,此时程序将奔溃。一个panic异常,有可能是开发者根据直接手动调用panic来触发,也有可能是程序运行时产生错误而抛出,比如常见的数组越界的异常就很容易触发panic异常;
复制代码


Recover is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

Here's an example program that demonstrates the mechanics of panic and defer:

Recover函数跟Panic息息相关,也是Go语言中的一个重要的内置函数,我们可以利用它将一个已经发生异常的goroutine恢复;Recover函数仅仅可以在defer延迟调用的执行体中使用;在程序正常执行时,recover 调用会返回nil不会产生其他影响;但如果当前的goroutine发生了panic异常,则recover会捕获触发panic时被赋的参数值并恢复程序的正常执行.
复制代码


译者注:以下是一个经典示例,

可以结合示例加深对defer,panic,recover的理解;

读者先自己尝试写出以下程序的输出,

然后对比分析自己的答案是否正确;

尤其 if r := recover(); r != nil { 这一行

最终r的值是什么?

答案就是上述的经典描述:

If the current goroutine is panicking,

a call to recover will capture the value

given to panic and resume normal execution.?


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)}
复制代码

The function g takes the int i, and panics if i is greater than 3, or else it calls itself with the argument i+1. The function f defers a function that calls recover and prints the recovered value (if it is non-nil). Try to picture what the output of this program might be before reading on.

函数g接受了参数i的值,并递归的调用了g本身;当i的值大于3时抛出Panic异常;函数f中定义了recover异常捕获;即如果r的值不为nil时,打印捕获的异常值;在继续往下阅读之前,您可以先试着描述下该程序的输出;
复制代码

The program will output:

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.
复制代码

If we remove the deferred function from f the panic is not recovered and reaches the top of the goroutine's call stack, terminating the program. This modified program will output:

如果将f函数中的defer 延迟调用的代码删除后执行,那么被触发的panic异常就无法正常恢复并且会将异常抛到当前goroutine堆栈的顶部并将当前程序终止;
复制代码


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]
复制代码

For a real-world example of panic and recover, see the json package from the Go standard library. It encodes an interface with a set of recursive functions. If an error occurs when traversing the value, panic is called to unwind the stack to the top-level function call, which recovers from the panic and returns an appropriate error value (see the 'error' and 'marshal' methods of the encodeState type in encode.go).

关于panic和recover的真实示例,可以参考Go标准库中的json包。 它使用一组递归函数对interface进行编码。 如果遍历该值时发生错误,则会调用panic将堆栈退回到顶层函数调用,该函数将从panic中恢复并返回适当的错误值(请参阅在encode.go中的encodeState类型的'error'和'marshal'方法 )。
复制代码

The convention in the Go libraries is that even when a package uses panic internally, its external API still presents explicit error return values.

Go库约定,及时程序包内使用了panic,其外部API仍会显示显示的error返回值.
复制代码

Other uses of defer (beyond the file.Close example given earlier) include releasing a mutex:

defer的其他用法(除了文件之外。在前面的close的示例)还包括释放互斥锁:
复制代码


https://golang.org/pkg/encoding/json/
复制代码


mu.Lock()defer mu.Unlock()
复制代码

printing a footer:

printHeader()defer printFooter()
复制代码

and more.

In summary, the defer statement (with or without panic and recover) provides an unusual and powerful mechanism for control flow. It can be used to model a number of features implemented by special-purpose structures in other programming languages. Try it out.

总而言之,defer语句(带有或不带有panic和recover)提供了一种异常强大的控制流机制。 我们可以使用defer很容易的实现和模拟在其他编程语言中使用特定的结构实现的许多功能。 不妨可试试。
复制代码


发布于: 2020 年 09 月 05 日阅读数: 107
用户头像

卓丁

关注

鸟过无痕 2017.12.10 加入

泰戈尔:虽然天空没有留下我的痕迹,但我已飞过。

评论

发布
暂无评论
[翻译]Defer,Panic,and Recover