专栏首页陌无崖知识分享Defer,Panic,and Recover

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官方博客部分文章的外文翻译,官方案例更加有料哦

本文分享自微信公众号 - golang技术杂文(gh_ebbdb61f463e),作者:无崖子天下无敌

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 聊聊golang的panic与recover

    codecraft
  • Go-defer,panic,recover

    defer 语法: defer function_name() 简单来讲,在defer所在函数执行完所有的代码之后,会自动执行defer的这个函数。 示例一(...

    李海彬
  • Go-defer,panic,recover

    defer 语法: defer function_name() 简单来讲,在defer所在函数执行完所有的代码之后,会自动执行defer的这个函数。 示例一(...

    李海彬
  • Go-defer,panic,recover

    defer 语法: defer function_name() 简单来讲,在defer所在函数执行完所有的代码之后,会自动执行defer的这个函数。 示例一(...

    李海彬
  • Golang 语言怎么使用 panic 函数?

    panic 是一个 Go 内置函数,它用来停止当前常规控制流并启动 panicking(运行时恐慌)过程。当函数 F 调用 panic 函数时,函数 F 的执行...

    frank.
  • Go语言panic/recover的实现

    本文主要分析Go语言的panic/recover在AMD64 Linux平台下的实现,包括:

    阿波张
  • go语言defer panic recover用法总结

    函数返回的过程是这样的:先给返回值赋值,然后再调用defer表达式,最后才是返回到调用函数中

    charlieroro
  • Golang 高效实践之defer、panic、recover实践

    我们知道Golang处理异常是用error返回的方式,然后调用方根据error的值走不同的处理逻辑。但是,如果程序触发其他的严重异常,比如说数组越界,程序就要直...

    用户2937493
  • 聊聊golang的panic与recover

    codecraft
  • 再读Golang中的异常处理 顶

    注意:如果一个没有recover的goroutine发生了panic,那么整个进程都会挂掉

    BGBiao
  • golang之panic,recover,defer

    recover内建函数用于“拦截”运行时恐慌,可以使当前的程序从恐慌状态中恢复并重新获得流程控制权。

    超蛋lhy
  • Golang语言捕获panic异常并转化为error

    package mainimport ( "fmt" "errors")func testPanic2Error() (err erro...

    李海彬
  • <Go语言学习笔记>【异常处理】

    通常 panic 和 recover 是用来处理异常问题的。我们来综述下,他们各自的特点:

    秦穆之
  • 6.Go-错误,defer,panic和recover

      defer最常用的就是关闭连接(数据库,文件等),可以打开连接后紧跟defer进行关闭

    zhang_derek
  • go panic与recover分析及错误处理

    error 是一种类型,表示错误状态的类型,如果没有错误则是nil。直白点将:error 类型就是描述错误的一种类型。

    地球流浪猫
  • panic 和 recover

    在 Go 语言中,程序中一般是使用错误来处理异常情况。对于程序中出现的大部分异常情况,错误就已经够用了。

    酷走天涯
  • Go语言错误与异常处理机制

    1 Error接口 Go语言中的error类型实际上是抽象了Error()方法的error接口

    李海彬
  • go 如何捕获异常

    王小明_HIT
  • Golang 语言中的 defer 怎么使用?

    在 Golang 语言中,我们可以在函数(自定义和部分内置)或方法中使用 defer 关键字注册延迟调用(一个或多个),多个延迟调用的执行顺序是先进后出(FIL...

    frank.

扫码关注云+社区

领取腾讯云代金券