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

 更新时间:2024年01月21日 15:48:36   作者: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使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang如何设置Header Content-type

    golang如何设置Header Content-type

    这篇文章主要介绍了golang如何设置Header Content-type问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Golang设计模式之适配器模式详细讲解

    Golang设计模式之适配器模式详细讲解

    这篇文章主要介绍了使用go实现适配器模式,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作,需要的朋友可以参考下
    2023-01-01
  • 在Go语言中使用JSON的方法

    在Go语言中使用JSON的方法

    这篇文章主要介绍了在Go语言中使用JSON的相关资料,需要的朋友可以参考下
    2018-02-02
  • Go语言语法基础之算术符示例详解

    Go语言语法基础之算术符示例详解

    这篇文章主要介绍了Go语言语法基础之算术符示例详解,详细讲解算术、关系、逻辑、位、赋值及其他运算符的用法与示例,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-06-06
  • 深入理解Go语言设计模式之函数式选项模式

    深入理解Go语言设计模式之函数式选项模式

    在 Go 语言中,函数选项模式(Function Options Pattern)是一种常见且强大的设计模式,用于构建可扩展、易于使用和灵活的 API,本文就来看看它的具体用法吧
    2023-05-05
  • golang如何用http.NewRequest创建get和post请求

    golang如何用http.NewRequest创建get和post请求

    这篇文章主要介绍了golang如何用http.NewRequest创建get和post请求问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Go中slog使用入门

    Go中slog使用入门

    slog是Go1.21引入的官方结构化日志库,本文就来介绍一下Go中slog使用入门,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-03-03
  • gin通过go build -tags实现json包切换及库分析

    gin通过go build -tags实现json包切换及库分析

    这篇文章主要为大家介绍了gin通过go build -tags实现json包切换及库分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • go select编译期的优化处理逻辑使用场景分析

    go select编译期的优化处理逻辑使用场景分析

    select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。接下来通过本文给大家介绍go select编译期的优化处理逻辑使用场景分析,感兴趣的朋友一起看看吧
    2021-06-06
  • Go语言通过反射实现获取各种类型变量的值

    Go语言通过反射实现获取各种类型变量的值

    反射是程序在运行期间获取变量的类型和值、或者执行变量的方法的能力,这篇文章主要为大家讲讲Go语言通过反射获取各种类型变量值的方法,需要的可以参考下
    2023-07-07

最新评论