Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言 defer使用

Go语言中defer使用的陷阱小结

作者:FrankGopher

本文主要介绍了Go语言中defer使用的陷阱小结,分别是defer语句不可以在return语句之后,defer语句执行的匿名函数,匿名函数的参数会被预先处理,具有一定的参考价值,感兴趣的可以了解一下

01 介绍

什么是 defer

defer 是Go语言提供的一种用于注册延迟调用的机制,以用来保证一些资源被回收和释放。

defer 注册的延迟调用可以在当前函数执行完毕后执行(包括通过return正常结束或者panic导致的异常结束)

当defe注册了的函数或表达式逆序执行,先注册的后执行,类似于栈 ”先进后出“

下面看一个例子:

package main

import "fmt"

func main() {
   f()
}

func f() {
   defer func() {
      fmt.Println(1)
   }()
   defer func() {
      fmt.Println(2)
   }()
   defer func() {
      fmt.Println(3)
   }()
}

输出:

3
2
1

如何使用defer

释放资源

使用 defer 可以在一定程度上避免资源泄漏,尤其是有很多 return 语句的场景,很容易忘记或者由于逻辑上的错误导致资源没有关闭。

下面的程序便是因为使用 return 后,关闭资源的语句没有执行,导致资源泄漏:

f, err := os.Open("test.txt")
if err != nil {
   return
}
f.process()
f.Close()

此处更好的做法如下:

f, err := os.Open("test.txt")
if err != nil {
   return
}
defer f.Close()

// 对文件进行操作
f,process()

此处当程序顺利执行后,defer 会释放资源;defer 需要先注册后使用,比如此处,打开文件异常时,程序执行到 return 语句时便会退出当前函数,没有经过 defer,所以此处defer 不会执行

defer 捕获异常

在 go 中没有 try 和 catch , 当程序出现异常是,我们需要从异常中恢复。我们这时可以利用 defer + recover 进行异常捕获

func f() {
    defer func() {
       if err := recover(); err != nil {
          fmt.Println(err)
       }
    }()
    // do something
    panic("panic")
}

注意,recover() 函数在在defer中用匿名函数调用才有效,以下程序不能进行异常捕获:

func f() {
    if err := recover(); err != nil {
        fmt.Println(err)
    }
    //  do something
    panic("panic")
}

实现代码追踪

下面提供一个方法能追踪到程序时进入或离开某个函数的信息,此处可以用来测试特定函数有没有被执行

func trace(msg string) { fmt.Println("entering:", msg) }
func untrace(msg string) { fmt.Println("leaving:", msg) }

记录函数的参数与返回值

有时候程序返回结果不符合预期是, 大家可能手动打印 log 调试,此时使用 defer 记录函数的参数和返回值,避免手动多处打印调试语句

func func1(s string) (n int, err error) {
    defer func() {
        log.Printf("func1(%q) = %d, %v", s, n, err)
    }()
    return 7, nil
}

实现代码追踪 和 记录函数的参数与返回值 

在 Go 语言中,defer 一般用于资源释放,或使用 defer 调用一个匿名函数,在匿名函数中使用 recover() 处理异常 panic。

在使用 defer 时,也很容易遇到陷阱,本文我们介绍使用 defer 时有哪些陷阱。

02 defer 陷阱

defer 语句不可以在 return 语句之后。

示例代码:

func main() {
    name := GetUserName("phper")
    fmt.Printf("name:%s\n", name)
    if name != "gopher" {
        return
    }
    defer fmt.Println("this is a defer call")
}

func GetUserName(name string) string {
    return name
}

输出结果:

name:phper

阅读上面这段代码,我们在 return 语句之后执行 defer 语句,通过输出结果可以发现 defer 语句调用未执行。

虽然 defer 可以在函数体中的任意位置,我们也是需要特别注意使用 defer 的位置是否可以执行。

defer 语句执行匿名函数,参数预处理。

示例代码:

func main() {
    var count int64
    defer func(data int64) {
        fmt.Println("defer:", data)
    }(count + 1)
    count = 100
    fmt.Println("main:", count)
}

输出结果:

main: 100
defer: 1

阅读上面这段代码,首先我们定义一个类型为 int64 的变量 count,然后使用 defer 语句执行一个匿名函数,匿名函数传递参数为 count + 1,最终 main 函数输出 100,defer 执行的匿名函数输出 1。

因为在执行 defer 语句时,执行了 count + 1,并先将其存储,等到 defer 所在的函数体 main 执行完,再执行 defer 语句调用的匿名函数的函数体中的代码。

03 总结

本文主要介绍在使用 defer 语句时可能会遇到的陷阱。分别是 defer 语句不可以在 return 语句之后;defer 语句执行的匿名函数,匿名函数的参数会被预先处理。

到此这篇关于Go语言中defer使用的陷阱小结的文章就介绍到这了,更多相关Go语言 defer使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文