Go 语言中的 defer 与 panic 的关系是什么?

王林
王林 原创
2023-06-11 09:00:08 246浏览

Go 语言是一种新式的编程语言,它新增了一些特色的语法和特性,其中 defer 和 panic 是其中两个非常重要的特性。本文将会介绍 Go 语言中 defer 和 panic 的关系以及它们的用法和特点。

defer 的用法

Go 语言中的 defer 语句用于注册一个函数,当这个函数执行结束或当前作用域结束时,会自动执行这个被注册的函数。 defer 可以用于释放资源、锁的解锁和错误处理等多个场景。

下面是一个 defer 释放资源的示例:

func main() {
    file, err := os.Open("myfile.txt")
  // 在函数结束时,会自动关闭文件
    defer file.Close()
  
    if err != nil {
        fmt.Println("Failed to open file.")
        return
    }
    // ...
}

通过 defer 文件的 Close() 函数被注册,当函数执行结束时会自动关闭文件。

下面是一个 defer 锁的解锁的示例:

func main() {
    var lock sync.Mutex
    lock.Lock()
  // 当函数执行结束时,会自动解锁
    defer lock.Unlock()
  
    // ...
}

当函数执行结束时会自动调用 Unlock() 函数解锁锁。

defer 的执行顺序是从后往前,这意味着如果多个 defer 语句被注册,它们会按照相反的顺序执行。下面示例会输出Defer 2,然后输出Defer 1

func main() {
    defer fmt.Println("Defer 1")
    defer fmt.Println("Defer 2")
    fmt.Println("Hello")
}

panic 的用法

Go 语言中的 panic 关键字用于抛出一个异常,并终止当前函数或者程序的执行。 panic 会沿着函数调用堆栈向上传递,直到被 recover() 函数捕捉到为止。如果没有被捕捉到,整个程序会被退出,并输出一个调用堆栈。

下面示例代码中,当输入的字符串长度小于 5 时,会触发 panic,终止程序的执行。

func hello(name string) {
    if len(name) < 5 {
        panic("Name is too short.")
    }
    fmt.Println("Hello", name)
}

func main() {
    hello("Tom")
    hello("Bob")
    hello("me")
}

输出结果如下:

Hello Tom
Hello Bob
panic: Name is too short.

goroutine 1 [running]:
main.hello(...)
    /Users/user/goland/src/main.go:4
main.main()
    /Users/user/goland/src/main.go:10 +0x81
exit status 2

这里我们可以看到当输入的 name 为me时,会触发 panic 并终止程序执行。

defer 和 panic 的关系

panic 起到立即终止程序执行的作用,这意味着它可以在任何时候触发,包括在函数执行结束之前。为了保证程序能够及时释放资源和执行一些必要的清理工作,Go 语言引入了 defer 这个机制,使得函数在退出之前能够先执行一些清理操作。

当一个函数中触发了 panic,它会立即退出,并执行当前函数之前注册的所有 defer 函数。下面这个示例代码手动触发了一个 panic,并在退出前执行了两次 defer 函数。

func main() {
    defer fmt.Println("Defer 1")
    defer fmt.Println("Defer 2")
    
    panic("Oops! Something went wrong.")
}

输出结果如下:

Defer 2
Defer 1
panic: Oops! Something went wrong.

goroutine 1 [running]:
main.main()
    /Users/user/goland/src/main.go:7 +0x81
exit status 2

我们可以看到在触发 panic 之后,两个 defer 函数被逆序执行。

除了可以在函数的最后注册 defer 函数,Go 语言还允许在函数中注册多次 defer 函数。这意味着如果一个函数中有多个 defer 函数,即使其中一个 defer 触发了 panic,也可以保证其它的 defer 函数仍然能够执行。

下面这个示例代码演示了当函数注册了多个 defer 语句时,其中一个 defer 函数触发了 panic,但其它的 defer 函数仍然执行的情况。

func init() {
    fmt.Println("Init 1")
}

func init() {
    fmt.Println("Init 2")
}

func main() {
    defer fmt.Println("Defer 1")
    
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recovered:", err)
        }
    }()
    
    defer fmt.Println("Defer 2")
    
    panic("Oops! Something went wrong.")
}

输出结果如下:

Init 1
Init 2
Defer 2
Recovered: Oops! Something went wrong.
Defer 1

我们可以看到,函数先执行了两个 init 函数,然后依次执行了三个 defer 函数。当其中一个 defer 触发了 panic,但是被另一个 defer 捕捉到并恢复了程序,最终两个 defer 函数都正常执行。

在实际开发中,defer 和 panic 往往是成对使用的,defer 用于释放资源和执行清理操作,panic 用于处理异常情况。当一个函数需要在退出之前执行多个清理操作时,我们可以在函数开头使用一个 defer 包装函数,并使用 recover() 函数避免函数提前退出。这种写法非常常见,也为我们编写健壮的程序提供了有力的保证。

以上就是Go 语言中的 defer 与 panic 的关系是什么?的详细内容,更多请关注php中文网其它相关文章!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。