设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间02 现在时间02 缓存数据 我们最后一次见面是一起看《摆渡人》里面有句台词:原来此刻坐在身边的人也会走散。他紧抓了我手一下,我莫名心里一沉。过了一周跟我说了分手。但是那个手的力度我还记得。

我们最后一次见面是一起看《摆渡人》里面有句台词:原来此刻坐在身边的人也会走散。他紧抓了我手一下,我莫名心里一沉。过了一周跟我说了分手。但是那个手的力度我还记得。 -- 空心

查看: 1281|回复: 3

超全讲解Golang中defer关键字的用法

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:221
  • 打卡月天数:0
  • 打卡总奖励:3547
  • 最近打卡:2025-03-21 17:08:49
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
424
主题
392
精华
0
金钱
4824
积分
861
注册时间
2023-1-6
最后登录
2025-5-31

发表于 2024-4-19 12:31:08 | 显示全部楼层 |阅读模式
目录


  • 1. 简介
  • 2. 问题引入
  • 3. defer对问题的解决

    • 3.1 defer基本介绍
    • 3.2 defer对上述问题的解决

  • 4.defer其他常见用途

    • 4.1 拦截和处理panic
    • 4.2 实现函数执行时间的计算

  • 5. defer相关特性

    • 5.1 defer的执行顺序
    • 5.2 注册defer函数时,其参数的求值时机

  • 6. defer注意点

    • 6.1 在defer中尽量避免执行可能引起panic的操作
    • 6.2 尽量避免在defer中使用闭包

  • 7. 总结

1. 简介

本文将从一个资源回收问题引入,引出
  1. defer
复制代码
关键字,并对其进行基本介绍。接着,将详细介绍在资源回收、拦截和处理panic等相关场景下
  1. defer
复制代码
的使用。
进一步,介绍
  1. defer
复制代码
的执行顺序,以及在注册
  1. defer
复制代码
函数时,其参数的求值时机等相关特性。最后,重点讲解
  1. defer
复制代码
的注意点,如在
  1. defer
复制代码
中函数中需要尽量避免引起panic,以及尽量避免在
  1. defer
复制代码
中使用闭包。
通过本文的阅读,读者将对Go语言中的
  1. defer
复制代码
有更深入的了解,并且能够更加有效地使用这个关键字。

2. 问题引入

开发过程中,函数可能会打开文件、建立网络连接或者其他需要手动关闭的资源。当函数在处理过程中发生错误时,我们需要手动释放这些资源。而如果有多处需要进行错误处理,手动释放资源将是一个不小的心智负担。同时,如果我们遗漏了资源的释放,就会导致资源泄漏的问题。这种问题可能会导致系统性能下降、程序运行异常或者系统崩溃等。
以下是一个示例代码,其中函数打开了一个文件,读取其中的内容并返回:
  1. func ReadFile(filename string) ([]byte, error) {
  2.     f, err := os.Open(filename)
  3.     if err != nil {
  4.         return nil, err
  5.     }

  6.     var content []byte
  7.     _, err = f.Read(content)
  8.     if err != nil {
  9.         // 出现错误,此时调用Close释放资源
  10.         f.Close()
  11.         return nil, err
  12.     }
  13.     // 正常处理结束,也需要调用Close释放资源
  14.     f.Close()
  15.     return content, nil
  16. }
复制代码
在上面的代码中,我们使用了
  1. os.Open
复制代码
函数打开一个文件,并在函数返回之前使用
  1. f.Close()
复制代码
函数手动关闭文件。同时,在出现错误时,我们也调用了
  1. f.Close()
复制代码
方法手动关闭了资源。
但是,我们设想一下,如果函数中不仅仅只打开了一个文件,而是同时打开了文件,网络连接,数据库连接等资源,同时假设函数中需要错误处理的地方有5处,此时在错误处理中,来实现对资源的回收是非常大的心智负担,而且一旦在某个错误处理中,忘记对资源的回收,那就代表着资源的泄漏,将会带来一系列的问题。而且,如果在函数执行过程中发生了
  1. panic
复制代码
,此时将不会执行错误处理函数,会直接退出,函数打开的文件可能将不会被关闭。
综上所述,我们这里遇到的问题,在于函数处理过程中,会打开一些资源,在函数退出时需要正确释放资源。而释放资源的方式,如果是在每一个错误处理处来对资源进行释放,此时对于开发人员是一个不小的负担;同时对于函数执行过程中发生
  1. panic
复制代码
的情况,也无法正常释放资源。
那有什么方式,能够简洁高效得释放资源,无需在函数的多个错误处理处都执行一次资源的回收;同时也能够处理
  1. panic
复制代码
可能导致资源泄漏的问题吗? 其实还真有,
  1. Go
复制代码
中的
  1. defer
复制代码
关键字便非常适合在该场景中使用,下面我先来了解了解
  1. defer
复制代码


3. defer对问题的解决


3.1 defer基本介绍

  1. Go
复制代码
语言中,我们可以在函数体中使用
  1. defer
复制代码
关键字,来延迟函数或方法的执行。
  1. defer
复制代码
延迟的函数或方法,会在当前函数执行结束时执行,无论函数是正常返回还是异常返回。也就是说,无论在函数中的哪个位置,只要使用了
  1. defer
复制代码
延迟执行了某个函数或方法,那么这个函数或方法的执行都会被推迟到当前函数执行结束时再执行。
  1. defer
复制代码
语句的语法很简单,它只需要在需要延迟执行的语句前加上
  1. defer
复制代码
关键字即可。
  1. defer
复制代码
语句支持执行函数调用和方法调用,也可以在语句中使用函数参数和方法参数等。下面是一个
  1. defer
复制代码
语句的示例:
  1. func demo() {
  2.     defer fmt.Println("deferred")
  3.     fmt.Println("hello")
  4. }
复制代码
在上面的示例中,我们使用了
  1. defer
复制代码
关键字,延迟了
  1. fmt.Println("deferred")
复制代码
的执行。当函数执行到
  1. defer
复制代码
语句时,这个语句并不会立即执行,而是被压入一个栈中,等到函数执行结束时,再按照后进先出的顺序依次执行这些被延迟的语句。在这个示例中,
  1. fmt.Println("hello")
复制代码
会先被执行,然后是被延迟的
  1. fmt.Println("deferred")
复制代码
。因此,输出的结果是:
  1. hellodeferred
复制代码
3.2 defer对上述问题的解决

通过上述描述,我们了解
  1. defer
复制代码
函数能够在函数或方法结束前延迟执行,而且无论函数是正常返回还是发生了
  1. panic
复制代码
  1. defer
复制代码
函数都会被执行。
这个特性非常适合用于资源的释放,例如打开的文件、建立的网络连接、申请的内存等等。我们可以在函数或方法中使用
  1. defer
复制代码
来延迟释放这些资源,从而避免因为忘记释放而导致的问题,同时也能够在发生异常时正确地释放资源,让代码更加健壮。下面我们使用
  1. defer
复制代码
对上面
  1. ReadFile
复制代码
函数进行改进,具体做法是在函数中使用defer关键字,将
  1. f.Close()
复制代码
操作延迟到函数结束时执行,代码如下:
  1. func ReadFile(filename string) ([]byte, error) {
  2.     f, err := os.Open(filename)
  3.     if err != nil {
  4.         return nil, err
  5.     }
  6.     // 获取到一个资源,便注册资源释放函数
  7.     defer f.Close()
  8.     var content []byte
  9.     _, err = f.Read(content)
  10.     if err != nil {
  11.         return nil, err
  12.     }
  13.     return content, nil
  14. }
复制代码
在之前的实现中,无论是正常结束还是出现错误,都需要调用
  1. f.Close()
复制代码
释放资源。而现在只需要通过
  1. defer
复制代码
关键字注册
  1. f.Close()
复制代码
函数即可,这样的代码更简洁,更容易维护,并且不会出现资源泄露的问题。

4.defer其他常见用途
  1. defer
复制代码
语句除了用于在函数中释放资源外,还有其他一些场景的用途,如拦截和处理
  1. panic
复制代码
,用于函数结束时打印日志等内容,下面将仔细对其进行说明。

4.1 拦截和处理panic

使用
  1. defer
复制代码
语句可以在程序出现
  1. panic
复制代码
时,及时进行资源回收和错误处理,避免程序因未处理的
  1. panic
复制代码
而直接崩溃。具体来说,可以通过在函数开头使用
  1. defer
复制代码
语句注册一个函数来捕获
  1. panic
复制代码
。当发生
  1. panic
复制代码
时,程序会先执行
  1. defer
复制代码
语句注册的函数,再进行
  1. panic
复制代码
的传递。
例如下面的代码中,函数中使用了
  1. defer
复制代码
来捕获
  1. panic
复制代码
,并在发生
  1. panic
复制代码
时进行了错误处理和资源回收:
  1. func someFunction() {
  2.     defer func() {
  3.         if r := recover(); r != nil {
  4.             log.Println("Recovered from panic:", r)
  5.             // 进行错误处理或者资源回收
  6.         }
  7.     }()
  8.     // 函数代码
  9.     // 可能会出现panic的代码
  10. }
复制代码
使用
  1. defer
复制代码
语句拦截和处理
  1. panic
复制代码
的好处是,在出现
  1. panic
复制代码
时,程序不会立即崩溃,而是可以通过
  1. defer
复制代码
语句进行错误处理和资源回收,保证程序的正常运行和数据的安全性。同时,这种方式也使得代码更加简洁易读,提高了代码的可维护性和可读性。

4.2 实现函数执行时间的计算

在性能测试和优化过程中,我们通常需要知道某个函数或代码段的执行时间。这个时候可以使用
  1. defer
复制代码
记录函数执行开始和结束的时间戳,然后计算两者之差,即可得到函数的执行时间。如下:
  1. func foo() {
  2.     defer func() {
  3.         fmt.Println("foo execution time:", time.Since(start))
  4.     }()
  5.     start := time.Now()
  6.     // 函数执行逻辑
  7. }
复制代码
在上述代码中,我们使用
  1. time.Now()
复制代码
函数获取当前时间戳,并将其存储在
  1. start
复制代码
变量中。然后,在函数执行结束时,我们在
  1. defer
复制代码
语句中定义一个匿名函数,用来计算函数执行时间并输出。在匿名函数中,我们调用
  1. time.Since(start)
复制代码
函数来获取当前时间戳与
  1. start
复制代码
变量之间的时间差,并将其输出。这样可以帮助我们快速发现程序中耗时较长的代码段,进而进行优化。
总的来说,
  1. defer
复制代码
的场景用途还是比较广泛的,可以在需要在函数执行结束后执行某些操作的场景下使用。

5. defer相关特性


5.1 defer的执行顺序

当函数中有多个
  1. defer
复制代码
语句时,它们的执行顺序是后进先出的,也就是说最后一个
  1. defer
复制代码
语句会最先执行,倒数第二个
  1. defer
复制代码
语句会在最后一个
  1. defer
复制代码
语句执行完后执行,以此类推。
例如,下面的代码中有三个defer语句:
  1. func main() {
  2.     defer fmt.Println("Third")
  3.     defer fmt.Println("Second")
  4.     defer fmt.Println("First")
  5.     fmt.Println("Hello, defer!")
  6. }
复制代码
当函数返回时,它们按照后进先出的顺序执行,所以输出结果是:
  1. Hello, World!FirstSecondThird
复制代码
5.2 注册defer函数时,其参数的求值时机

在注册
  1. defer
复制代码
函数时,如果
  1. defer
复制代码
函数传入的参数是变量,那么变量的求值顺序与普通函数调用一样,是在函数参数传递之前进行的。例如,假设有如下代码:
  1. func foo() {
  2.     a := 1
  3.     defer func(x int) {
  4.         fmt.Println("x in defer:", x)
  5.     }(a)
  6.     a = 2
  7.     fmt.Println("a before end of function:", a)
  8. }
复制代码
在这个例子中,变量a在
  1. defer
复制代码
函数中被作为参数传递,
  1. defer
复制代码
语句中的匿名函数会捕获a的值,并在函数执行结束时打印该值。
  1. foo
复制代码
函数执行的结果如下:
  1. a before end of function:2x in defer:1
复制代码
因此,可以看出在
  1. defer
复制代码
语句中传入的变量是在注册
  1. defer
复制代码
函数时进行求值的,而不是在函数执行结束时。

6. defer注意点


6.1 在defer中尽量避免执行可能引起panic的操作

在使用
  1. defer
复制代码
语句时,应当尽量避免在其中引起
  1. panic
复制代码
。因为当在
  1. defer
复制代码
语句中发生
  1. panic
复制代码
时,当前
  1. defer
复制代码
函数中后续的语句将无法得到执行,可能无法释放已经申请的资源。此时,程序可能会因为资源泄漏等问题而崩溃或产生其他不可预期的后果。举个例子,假设有如下代码:
  1. func main() {
  2.     defer func() {
  3.        if r := recover(); r != nil {
  4.           fmt.Println("Recovered in defer:", r)
  5.        }
  6.     }()
  7.     fmt.Println("Start")
  8.     defer fmt.Println("First Defer")
  9.     defer func() {
  10.        fmt.Println("Second Defer")
  11.        panic("oops")
  12.        fmt.Println("资源回收")
  13.     }()
  14.     fmt.Println("End")
  15. }
复制代码
这段代码中,我们在第三个
  1. defer
复制代码
语句中引发了
  1. panic
复制代码
,这时会触发
  1. panic
复制代码
机制,第三个
  1. defer
复制代码
后续的代码将不会被执行,最后程序会输出如下结果:
  1. StartEndSecond DeferFirst DeferRecovered in defer: oops
复制代码
可以看到,第三个
  1. defer
复制代码
语句中,由于
  1. panic
复制代码
导致了
  1. fmt.Println("资源回收")
复制代码
语句无法被执行。因此,在编写代码时,我们应该尽量避免在
  1. defer
复制代码
中引起
  1. panic
复制代码
,如果不可避免有
  1. panic
复制代码
可能性的出现,此时应该对其进行处理,以确保程序的稳定性和可靠性。

6.2 尽量避免在defer中使用闭包

这里先简单介绍下闭包,在 Go 中,闭包是一个函数值(function value),它引用了函数体之外的变量。这个被引用的变量会被“捕获”到闭包中,即使这个变量在闭包被创建之后发生了变化,闭包中也能访问到变化后的值。
  1. defer
复制代码
中使用闭包可能会导致一些意想不到的问题。因为闭包引用了外部变量,而在
  1. defer
复制代码
函数执行时,这些变量的值可能已经被修改或者不再存在,从而导致出现不可预期的行为。
举个例子,假设有一个defer函数使用了闭包来记录当前时间戳和某个变量的值:
  1. func foo() {
  2.     i := 0
  3.     defer func() {
  4.         fmt.Printf("i: %d, timestamp: %d\n", i, time.Now().UnixNano())
  5.     }()
  6.     i++
  7. }
复制代码
在这个例子中,我们使用了闭包来捕获了变量
  1. i
复制代码
和当前时间戳,并在
  1. defer
复制代码
函数中输出它们的值。然而,由于
  1. defer
复制代码
函数的执行时机是在函数返回之后,我们无法确定变量
  1. i
复制代码
的值是否已经被修改了。因此,这个例子可能输出的结果是不稳定的,无法得到预期的结果。
因此,尽量避免在
  1. defer
复制代码
中使用闭包,可以避免一些潜在的问题。如果必须要使用闭包,那么要格外小心,确保在
  1. defer
复制代码
函数执行时闭包引用的变量值仍然是符合预期的。

7. 总结

在本文中,我们从一个资源回收的问题引出了defer,介绍了defer的基本用法以及在资源回收、拦截和处理panic等场景中的使用。我们还讨论了defer的一些特性,如执行顺序以及注册defer函数时,参数的求值时机。最后,我们提醒了在使用defer时需要注意的一些问题,如尽量避免在defer中引起panic和避免在defer中使用闭包。
总的来说,defer是Go语言中一个非常方便和强大的语法特性,在某些场景下可以帮助我们更好地实现某些功能。但是,在使用defer时需要注意一些问题,避免引起不必要的麻烦。掌握defer的使用技巧,可以让我们的代码更加健壮、清晰和易于维护。
以上就是超全讲解Golang中defer关键字的用法的详细内容,更多关于Golang defer关键字的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-29
最后登录
2022-12-29

发表于 2024-11-20 11:59:55 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
18
积分
16
注册时间
2022-12-25
最后登录
2022-12-25

发表于 2025-3-15 13:56:13 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-30
最后登录
2022-12-30

发表于 2025-4-18 14:12:38 | 显示全部楼层
感谢楼主,顶。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
4楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表