原文链接 : http://www.bugclosed.com/post/17

defer机制

go语言中的defer提供了在函数返回前执行操作的机制,在需要资源回收的场景非常方便易用(比如文件关闭,socket链接资源十分,数据库回话关闭回收等),在定义资源的地方就可以设置好资源的操作,代码放在一起,减小忘记引起内存泄漏的可能。

defer机制虽然好用,但却不是免费的,首先性能会比直接函数调用差很多;其次,defer机制中返回值求值也是一个容易出错的地方。

一个简单的性能对比测试

通过一个对锁机制的defer操作来比较性能差异。

package main

import (
"sync"
"testing"
) var (
lock = new(sync.Mutex)
) func lockTest() {
lock.Lock()
lock.Unlock()
}
func lockDeferTest() {
lock.Lock()
defer lock.Unlock()
}
func BenchmarkTest(b *testing.B) {
for i := 0; i < b.N; i++ {
lockTest()
}
}
func BenchmarkTestDefer(b *testing.B) {
for i := 0; i < b.N; i++ {
lockDeferTest()
}
}

运行命令go test -v -test.bench, 性能对比测试结果如下:

BenchmarkTest-8        	100000000	        18.5 ns/op
BenchmarkTestDefer-8 20000000 56.4 ns/op

从测试结果可以看出,Defer版本的lock操作时间消耗几乎是函数直接调用的3倍以上。

defer执行顺序和返回值求值

看一个简单的测试:

package main

import (
"fmt"
) func test_unnamed()(int) {
var i int
defer func() {
i++
fmt.Println("defer a:", i)
}()
defer func() {
i++
fmt.Println("defer b :", i)
}()
return i
}
func test_named()(i int) {
defer func() {
i++
fmt.Println("defer c:", i)
}()
defer func() {
i++
fmt.Println("defer d :", i)
}()
return i
} func main() {
fmt.Println("return:", test_unnamed())
fmt.Println("return:", test_named())
}

执行结果是:

defer b : 1
defer a: 2
return: 0
defer d : 1
defer c: 2
return: 2

关于同时有多个defer时的执行顺序,可以看做是go编译器为每个函数维护了一个先进后出的堆栈。每次遇到defer语句就讲执行体封装后压入堆栈中,等到函数返回时,从堆栈中依次出栈执行。所以 “defer b”语句在后,却先调用。

关于函数求值问题,可以将test_unnamed函数返回和defer的执行和求值理解为3个步骤:

  1. 运行到“return i“语句时,取值当前i值,赋值给test_unnamed返回值,得到函数test的返回值0(因为test_unnamed中只定义了i,并未操作,i保留成初始默认值)。
  2. 按照先进后出的方式,一次调用defer语句执行。
  3. 执行真正的test_unnamed 函数返回 ”return“。

以上是分析了匿名返回值的情况,具名返回值test_named的情况稍有不同,return 返回了2,而不是0,因为defer函数中对返回值变量i做了修改。

由此可见,使用多个defer和defer函数中还需要处理返回值的情况下极容易出问题,使用时需要小心谨慎。

defer释放锁

通过defer释放锁(sync.Mutex)是很常见的场景,示例如下:

def GetMapData(key uint32) uint32{
lock.Lock()
defer lock.Unlock()
if v, ok := mapData[key]; ok{
return v
}
return 0
}

在这样简单的场景下,通过defer直接释放锁,在后续的代码逻辑基本可以忘记锁的存在而写代码。但是这种模式就存在一个锁粒度的问题--整个函数都被锁住了。

如果lock后面还有很多复杂或者阻塞的逻辑(写日志,访问数据库,从ch读取数据等),会导致锁的持有时间过大,影响系统的处理性能;此时可以精细控制逻辑函数的分拆,让锁尽量只控制共享资源,抛弃defer自行控制unlock,以免锁粒度过大。

总结

defer是一个很强大的机制,尤其是在资源释放的场景特别适用。但是使用时要注意,defer是有不小的性能损耗,且过度使用后也会导致逻辑变复杂。

拒绝滥用golang defer机制的更多相关文章

  1. Golang反射机制

    Go反射机制:在编译不知道类型的情况下,可更新变量.在运行时查看值.调用方法以及直接对它们的布局进行操作. 为什么使用反射 有时需要封装统一接口对不同类型数据做处理,而这些类型可能无法共享同一个接口, ...

  2. Golang超时机制--2秒内某个函数没被调用就认为超时

    Golang超时机制--2秒内某个函数没被调用就认为超时 需求描述 当一整套流程需要其他程序来调用函数完成时通常需要一个超时机制,防止别人程序故障不调你函数导致你的程序流程卡死 实现demo pack ...

  3. golang defer 延后执行什么

    对于golang的defer,我们已经知道,defer定义的语句可以延后到函数返回时执行. 经常用在文件的关闭,锁的释放等场景中.而且defer定义的语句即使遇到panic也会执行.这样,可以执行必要 ...

  4. 浅谈 Golang 插件机制

    我们知道类似 Java 等半编译半解释型语言编译生成的都是类似中间态的字节码,所以在 Java 里面我们想要实现程序工作的动态扩展,可以通过 Java 的字节码编辑技术([[动态代理#ASM]]/[[ ...

  5. golang defer的使用

    defer一般用于在函数结束时执行必要的处理工作.例如,关闭文件描述符,关闭网络连接等等. 函数中可以定义多个defer,执行的时候按照先进后出的顺序. defer定义的语句,即使遇到panic,也会 ...

  6. golang defer使用——资源关闭时候多用

    defer Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句.当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回.特别是当你在进行一些打开资源 ...

  7. golang defer那些坑

    defer以下几个特性,使用时需要关注下. 即时的参数传递 调用os.Exit()时defer不会被执行 defer与return的先后顺序 1.即时的参数传递 定义defer时传入的参数,是作为拷贝 ...

  8. golang defer 以及 函数栈和return

    defer 作为延迟函数存在,在函数执行结束时才会正式执行,一般用于资源释放等操作 参考一段代码https://mp.weixin.qq.com/s/yfH0CBnUBmH0oxfC2evKBA来分析 ...

  9. 探究 Go 语言 defer 语句的三种机制

    Golang 的 1.13 版本 与 1.14 版本对 defer 进行了两次优化,使得 defer 的性能开销在大部分场景下都得到大幅降低,其中到底经历了什么原理? 这是因为这两个版本对 defer ...

随机推荐

  1. REST接口设计规范总结

    简介 Representational State Transfer 简称 REST 描述了一个架构样式的网络系统.REST 指的是一组架构约束条件和原则.满足这些约束条件和原则的应用程序或设计就是 ...

  2. c++getline()、get()等

    1.cin 接受一个字符串,遇“空格”.“TAB”.“回车”都结束 2.cin.get() cin.get(字符变量名)可以用来接收字符 只能接收一个字符 cin.get(字符数组名,接收字符数目)用 ...

  3. P1120 小木棍 [数据加强版]

    题目描述 乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50. 现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度. 给出每段小木棍的长度,编 ...

  4. 通过ReentrantLock简单了解下并发包中的锁

    ReentrantLock在进行实例化时,可以通过构造函数的参数选择是否使用公平锁FairSync或者非公平锁NonfairSync,两者的区别比较简单,如果是公平锁则新来的线程会先检测同步队列中是否 ...

  5. C#做一个简单的进行串口通信的上位机

    C#做一个简单的进行串口通信的上位机   1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来操作数据.上位机可以接收下位机的信号.下位机是 ...

  6. ios学习路线—Objective-C(Runtime消息机制)

    RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何 ...

  7. Tarjan算法初探 (1):Tarjan如何求有向图的强连通分量

    在此大概讲一下初学Tarjan算法的领悟( QwQ) Tarjan算法 是图论的非常经典的算法 可以用来寻找有向图中的强连通分量 与此同时也可以通过寻找图中的强连通分量来进行缩点 首先给出强连通分量的 ...

  8. 使用Jquery Viewer 展示图片信息

    <!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...

  9. day 85 Vue学习之vue-cli脚手架下载安装及配置

      1. 先下载node.js,下载地址:https://nodejs.org/en/download/ 找个目录保存,解压下载的文件,然后配置环境变量,将下面的路径配置到环境变量中. 由于 Node ...

  10. 2017-2018-1 20155234 实验三 实时系统及mypwd实现

    2017-2018-1 20155234实验三实时系统及mypwd实现 实验三-并发程序-1 学习使用Linux命令wc(1) 基于Linux Socket程序设计实现wc(1)服务器(端口号是你学号 ...