likes
comments
collection
share

Go 语言开发者必须知道的 defer 语句小技巧

作者站长头像
站长
· 阅读数 10

Go 语言中的 defer 语句是一种非常有用的机制,它可以在函数返回之前执行某些操作。这种机制主要用于确保在函数结束时清理资源,关闭文件等。

如何使用

Go 语言中的 defer 语句允许我们推迟函数或方法的执行,直到包含它的函数返回。它的语法如下:

defer function_name()

这个语句会将 function_name() 函数推迟到包含它的函数返回之前执行。defer 语句的执行顺序是 “先进后出”,也就是说,最后一个 defer 语句会最先执行,而第一个 defer 语句会最后执行。

defer 语句执行顺序

defer语句的执行顺序是“后进先出”,即最后一个defer语句会最先执行,而最先声明的defer语句会最后执行。下面是一个示例代码:

func main() {
    defer fmt.Println("Three")
    defer fmt.Println("Two")
    defer fmt.Println("One")
}

输出结果为:

One
Two
Three

在上面的代码中,三个defer语句的执行顺序是 “One”、“Two”、“Three”,因为它们是按照后进先出的顺序执行的。

底层实现原理

在 Go 语言中,每个函数都有一个 defer 调用栈,用于存储该函数中出现的 defer 语句。当函数返回时,Go 语言会按照“后进先出”的顺序执行 defer 语句。这意味着最后一个 defer 语句会最先执行。

defer 语句是通过 runtime.deferproc() 函数实现的。当遇到 defer 语句时,Go 语言会将要执行的函数和参数压入 defer 调用栈中,并调用 runtime.deferproc() 函数。该函数会记录 defer 语句的相关信息,并将其添加到 defer 调用栈中。

当函数返回时,Go 语言会按照“后进先出”的顺序执行 defer 语句。具体来说,Go 语言会调用 runtime.deferreturn() 函数,该函数会遍历 defer 调用栈,依次执行其中的 defer 语句。在执行 defer 语句时,Go 语言会将其相关信息从 defer 调用栈中删除,以确保每个 defer 语句只会执行一次。

需要注意的是,defer 语句会在当前函数返回时执行,而不是在整个程序退出时执行。因此,如果在函数内部使用了 defer 语句,该语句可能会在程序还没有完全退出时就被执行了。

如果你想详细了解 defer 语句的实现原理,可以参考 Go 语言官方文档中的相关内容:golang.org/doc/effecti…

defer 语句有哪些应用场景?

资源清理

defer 语句最常见的用途是在函数结束时清理资源。例如,一个打开文件的函数可能需要在函数结束时关闭这个文件,以避免资源泄漏。我们可以使用 defer 语句来确保在函数结束时关闭文件,无论函数是正常返回还是出现错误。下面是一个使用 defer 语句关闭文件的例子:

func readFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()

    // 读取文件并进行处理
    // ...

    return nil
}

在这个例子中,我们使用 defer 语句来确保在 readFile() 函数结束时关闭文件。如果在读取文件时出现错误导致函数提前返回,defer 语句仍然会在函数结束时执行,从而确保文件被关闭。

Panic 和 Recover

Go 语言中的 panic 和 recover 机制可以用于处理程序的错误和异常情况。当程序出现错误时,我们可以使用 panic 函数来抛出一个异常,并且程序会在当前函数中断执行,转而执行当前函数的 defer 语句。在 defer 语句中,我们可以使用 recover 函数来捕获异常并进行处理。下面是一个简单的例子:

func test() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()
    panic("test panic")
}

在这个例子中,我们在 test() 函数中使用了 panic 函数来抛出一个异常。在 defer 语句中,我们使用 recover 函数来捕获异常,并打印出异常信息。当程序执行到 test() 函数时,它会抛出一个异常并中断执行,然后转而执行 defer 语句中的代码,最后打印出异常信息。

统计函数计算耗时

我们可以在 defer 语句中执行一些统计操作的示例代码如下:

func test() {
    start := time.Now()
    defer func() {
        duration := time.Since(start)
        fmt.Printf("test took %s\\n", duration)
    }()
    // 执行一些操作
    // ...
}

在这个例子中,我们在 test() 函数中使用 defer 语句来计算函数的执行时间。在函数开始时,我们使用 time.Now() 函数来记录当前时间。然后,在 defer 语句中,我们使用 time.Since() 函数来计算函数的执行时间,并将其打印出来。

通过这种方式,我们可以在函数结束时得到函数的执行时间,并根据这些信息来优化程序的性能。

关于 defer 语句使用,有哪些使用注意事项

  • defer 语句的执行时间是在当前函数返回之前,而不是整个程序退出之前。因此,在函数内部使用 defer 语句时,该语句可能会在程序还没有完全退出时就被执行了。
  • defer 语句的执行顺序是“后进先出”,也就是说,最后一个 defer 语句会最先执行,而第一个 defer 语句会最后执行。这一点需要特别注意,因为在函数内部有多个 defer 语句时,它们的执行顺序可能会影响程序的行为。
  • defer 语句只能用于函数或方法的局部作用域内。如果在全局作用域内使用 defer 语句,编译器会报错。
  • 如果在 defer 语句中使用了函数参数,需要注意函数参数的值是在 defer 语句出现时确定的,而不是在代码实际执行时确定的。因此,在使用 defer 语句时,需要特别注意函数参数的值是否符合预期。

除了上述注意事项之外,就是它只能用于一些特定的场合,如资源清理和异常处理。如果没有合适的场景,滥用 defer 语句反而会让代码变得更加混乱和难以维护。

总结

总之,defer 语句是一种非常有用的机制,它可以帮助我们更好地编写高质量的 Go 代码。虽然它的主要用途是用于资源清理和异常处理,但是,只要我们善于利用,它还有很多其他的应用场景。因此,在编写 Go 代码时,我们应该充分利用这种机制,以便更好地提高代码的质量和效率。

转载自:https://juejin.cn/post/7213915505487790117
评论
请登录