Go语言defer,Panic,Recover

Go 具有常用的控制流机制:if、for、switch、goto。它还具有 go 语句来在单独的 goroutine 中运行代码。在这里我想讨论一些不太常见的问题:defer,Panic,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
}

这可行,但有一个错误。如果对 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 语句被求值时,延迟函数的参数也会被求值。在此示例中,当 Println 调用被延迟时,将计算表达式“i”。延迟调用将在函数返回后打印“0”。
func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return

  • 延迟函数调用在周围函数返回后按后进先出顺序执行。该函数打印“3210”:
func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}
  • 延迟函数可以读取并分配给返回函数的命名返回值。在此示例中,延迟函数在 周围函数返回递增返回值 i。因此,该函数返回 2:​​​​​​
func c() (i int) {
    defer func() { i++ }()
    return 1
}

Panic是一个内置功能,可以停止普通的控制流程并开始panic。当函数F调用panic时,F的执行停止,F中的任何延迟函数都正常执行,然后F返回到其调用者。该过程继续在堆栈中向上进行,直到当前 goroutine 中的所有函数都返回,此时程序崩溃。可以通过直接调用panic来引发panic。它们也可能是由运行时错误引起的,例如越界数组访问。

Recover是一个内置函数,可以重新获得对发生panic的 goroutine 的控制。恢复仅在延迟函数内有用。在正常执行期间,调用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 延迟了一个调用恢复并打印恢复值(如果它非零)的函数。在继续阅读之前,尝试想象一下该程序的输出可能是什么。

程序将输出:

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 中删除延迟函数,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]

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

Go 库中的约定是,即使包在内部使用panic,其外部 API 仍然会显示显式的错误返回值。

defer的其他用途(除了前面给出的 file.Close 示例)包括释放互斥体:

mu.Lock()
defer mu.Unlock()

总之,defer 语句(有或没有panic 和recover)为控制流提供了一种不寻常且强大的机制。

 

  • 20
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术的草台班子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值