Golang关键字——defer

一、defer解决什么问题?

试想下,我们在一个函数中对临界区执行加锁操作,但是,临界区的代码可能会异常退出(如下图所示)。

为了防止这种情况,我们需要在每个err后面,都加上一个l.Unlock,这样的代码也难看了。为了解决这个问题,就需要这样的一个功能:能在函数、方法执行完毕后,进行一些收尾操作,这些操作可能是释放资源、或捕获panic错误,所以就有了defer这个关键字;

二、defer注意点

关于defer源码方面的信息,我觉得了解以下两点即可:

  • 知道defer函数会包装成一个_defer结构,挂载到相应的协程上;
  • 知道看汇编代码,找到defer的运行时函数runtime.deferreturn;

**【注】**没必要陷入看defer源码上实现上,了解以下这部分,就够了。如果开发过程中,有需求、有精力再去深究细节。

预参数计算

预参数计算,就是说再运行到defer函数的时候,它的参数就已经确定了。像下面这个例子打印出来的i为0

go 复制代码
func a() {
    i := 0
    defer fmt.Println(i)
    i++
    return
}

Go Playground

LILO

go 复制代码
func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i)
    }
}

LILO,表示的是当运行到defer的时候,runtimedefer函数打包成一个_defer挂在到当前协程的_defer字段。对于上述代码,goroutine的图如下所示。当函数执行完时,从后到前执行对应链表,4,3,2,1

修改函数具名返回值

go 复制代码
func c() (i int) {
    defer func() { i++ }()
    return 1
}

上述代码,输出2。原因是,函数返回的1,先复制具名函数i,然后执行i++。「这部分有疑问,可以看下Golang函数布局」

三、defer性能损耗

defer带来方便的同时,也会带来一定的性能损耗。这个我们可以通过Benchmark来验证下。我们只需要了解有损耗即可,这点损耗对于大部分业务来说是没有影响的;

go 复制代码
func sum(max int) int {
   total := 0
   for i := 0; i < max; i++ {
      total += i
   }

   return total
}

func fooWithDefer() {
   defer func() {
      sum(10)
   }()
}

func fooWithOutDefer() {
   sum(10)
}

func BenchmarkFooWithDefer(b *testing.B) {
   for i := 0; i < b.N; i++ {
      fooWithDefer()
   }
}

func BenchmarkFooWithOutDefer(b *testing.B) {
   for i := 0; i < b.N; i++ {
      fooWithOutDefer()
   }
}
go 复制代码
➜  defer git:(master) ✗ go test -bench .                         
goos: darwin
goarch: amd64
pkg: go-tool/basic/defer
BenchmarkFooWithDefer-12        172309779                6.91 ns/op
BenchmarkFooWithOutDefer-12     249897656                4.76 ns/op
PASS
ok      go-tool/basic/defer     4.080s

总结

本文主要描述关于defer对的以下几点:

  1. defer解决什么问题?
  2. defer三个重要的特性,分别是预参数计算、LILO、修改函数具名返回值;
  3. 通过Benchmark了解defer带来的性能损耗;

参考

相关推荐
程序员契奇17 小时前
Tools工具使用
人工智能·后端
IT_陈寒18 小时前
SpringBoot自动配置没生效?你可能漏了这个注解
前端·人工智能·后端
长明18 小时前
C#项目组织与概念梳理
后端·c#
xn713318 小时前
个人网站站外分发怎么做归因?我给 XBSTACK 补了一套 UTM 追踪规则
后端·低代码
用户23307130747918 小时前
JUC 并发容器与工具
后端
冰暮流星18 小时前
flask之模版渲染
后端·python·flask
威武的花瓣18 小时前
细说ASP.NET的各种异步操作
后端·asp.net·php
漂亮的摩托18 小时前
如何编写一个SpringBoot项目告警推送的Starter
java·spring boot·后端
任性的芝麻18 小时前
ASP.NET MVC 中的异步方式
后端·asp.net·mvc
雨师@19 小时前
go语言项目--实例化(图书管理)--006
开发语言·后端·golang