Golang脱俗的defer

  • 浅谈defer

GO中的defer一直都是项目中常用的依赖。无论是解锁,还是关闭文件,或者关闭session,大多都离不开defer。稍微使用过golang的粉(huan)丝(zhe)应该都知道,程序即使panic,defer也依然会照常执行。但是与传统中逐行执行相比,defer会造成较大的开销,这也是被行业中所诟病的痛点。相较于讲how,我们不如谈谈why吧,相比于语法,我觉得我们大可讨论下实现原理,来增加些“驾驶乐趣”。

说之前,我们简要的看下defer的实现原理哈~。先上一段代码吧:

package mainfunc main() {defer func() {print("hello, I'm xyp")}()return
}

我们分别使用两个版本的Go进行编译(1.13和1.14)。

使用golang的反汇编工具进行反汇编:

go tool compile -S main.go

不处所料,汇编代码中对defer的引用以及return都有所不同。

1.13反汇编后的结果

0x0036 00054 (main.go:7)        MOVL    $0, (SP)
        0x003d 00061 (main.go:7)        LEAQ    "".main.func1·f(SB), AX
        0x0044 00068 (main.go:7)        MOVQ    AX, 8(SP)
        0x0049 00073 (main.go:7)        PCDATA  $1, $0
        0x0049 00073 (main.go:7)        CALL    runtime.deferproc(SB)
        0x004e 00078 (main.go:7)        TESTL   AX, AX
        0x0050 00080 (main.go:7)        JNE     84
        0x0052 00082 (main.go:7)        JMP     34
        0x0054 00084 (main.go:7)        XCHGL   AX, AX

1.14反汇编后的结果

0x004f 00079 (main.go:6)        PCDATA  $1, $-1
        0x004f 00079 (main.go:6)        PCDATA  $0, $-2
        0x004f 00079 (main.go:6)        CALL    runtime.morestack_noctxt(SB)
        0x0054 00084 (main.go:6)        PCDATA  $0, $-1
        0x0054 00084 (main.go:6)        JMP     0

由此可见,1.13及之前的版本,defer都是在堆上分配,而1.14及之后的版本,defer分配在栈上。

  • 再探究竟

Go语言中,每个goroutine都有自己的一个defer链表,而runtime.deferproc函数做的事情就是把defer函数及其参数添加到链表中,即我们所谓的注册。然后编译器还会在当前函数结尾处插入runtime.deferreturn的调用代码,后者会按照LIFO的顺序调用当前函数注册的所有defer函数。如果当前goroutine发生了panic,或者调用了runtime.Goexit,runtime会按照LIFO的顺序遍历整个defer链表逐一执行defer函数,直到某个defer函数完成了recover,或者最后程序退出。

deferproc的函数原型如下:

// Create a new deferred function fn with siz bytes of arguments.
// The compiler turns a defer statement into a call to this.
//go:nosplit
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fngp := getg()if gp.m.curg != gp {// go code on the system stack can't deferthrow("defer on system stack")}// the arguments of fn are in a perilous state. The stack map// for deferproc does not describe them. So we can't let garbage// collection or stack copying trigger until we've copied them out// to somewhere safe. The memmove below does that.// Until the copy completes, we can only call nosplit routines.sp := getcallersp()argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)callerpc := getcallerpc()d := newdefer(siz)if d._panic != nil {throw("deferproc: d.panic != nil after newdefer")}d.link = gp._defergp._defer = dd.fn = fnd.pc = callerpcd.sp = spswitch siz {case 0:// Do nothing.case sys.PtrSize:*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))default:memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))}// deferproc returns 0 normally.// a deferred func that stops a panic// makes the deferproc return 1.// the code the compiler generates always// checks the return value and jumps to the// end of the function if deferproc returns != 0.return0()// No code can go here - the C return register has// been set and must not be clobbered.
}

该function有两个input, siz即为编译器自动添加, 而fn指向一个runtime.funcval结构,里面有defer函数的地址

假设一段code如下:

func f1() {defer func() {print("hello world")}()print("hello xyp")
}

编译器会将其转为伪代码大致如下

func f1() {r := runtime.deferproc(16, f1_func1, 10, 20) // recover后,返回时r为1,否则为0if r > 0 {goto ret}print("hello xyp")runtime.deferreturn()return
ret:runtime.deferreturn()
}// f1的defer函数
func f1_func1() {print("hello world")
}

Golang脱俗的defer相关推荐

  1. [Golang]一道考察defer与命名返回值的题目

    题目 输出: 4 1 3 解释 当函数有可命名结果形参时,结果形参的初始值被设置为零值,函数的return语句会设置结果形参的值 当函数有可命名结果形参时,defer函数是可以修改它,然后再将它的值返 ...

  2. golang中的defer

    defer在go语言中可以发挥很大的作用,在函数中定义的defer会放在return前执行,defer后面可以放一些资源关闭的操作,以防忘记关闭资源而浪费空间. package mainimport ...

  3. Golang——延迟调用defer

    defer用于向当前函数注册稍后执行的函数调用.这些调用被称作延迟调用,它们直到当前函数执行结束前才被执行,常用于资源释放.错误处理等操作 func main() {f, err := os.Open ...

  4. golang defer的使用

    在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内 ...

  5. Go实战--golang中defer的使用

    原址 生命不止,继续 go go go !!! 学习golang这么久了,还没看到类似传统的 try-catch-finally 这种异常捕捉方式.  但是,Go中引入的Exception处理:def ...

  6. golang学习之四:闭包、defer

    golang闭包.defer 闭包 闭包以引用的方式捕捉外部变量 闭包变量与作用域 传统函数局部变量 闭包的变量 defer 延迟:在函数执行完毕之前调用 多个defer执行顺序 闭包 闭包以引用的方 ...

  7. 理解Golang中defer的使用

    之前一直对Go中的defer不太理解,所以我单独弄出来整理一下. 在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是r ...

  8. twitter.common.concurrent deadline and defer

    此defer非golang中的defer https://tour.golang.org/flowcontrol/12 from twitter.common.concurrent import Ti ...

  9. golang相关知识总结

    实数 三个语句效果一样: days := 365.2425 var days=365.2425 var days float64 = 365.2425 //只要含有小数部分,那么它的类型就是浮点类型f ...

最新文章

  1. 牛客练习赛64 - B Dis2(树,基础图论)
  2. 【详细】Android入门到放弃篇-YES OR NO-》各种UI组件,布局管理器,单元Activity
  3. 4.4 Triplet 损失-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  4. 力扣——整数反转(Java)
  5. 控制台程序隐藏方法总结(四种)
  6. 背包问题 codevs2210 数字组合
  7. 如何使柱状图左右展示_Excel多次层柱状图,让数据展示更清晰,简单五步就完成...
  8. source:读取文件 “/etc/profile” 时发生错误解决办法
  9. 【CSWS2014 Main Conference】Some Posters
  10. mysql命令的依赖库_3.EZMM工程(常用shell命令,及需要用到的基本依赖库)
  11. 136.Single Number
  12. PHP中self和static的区别,php面向对象程序设计中self与static的区别分析
  13. 有双面打印功能的打印机,安装驱动后,无法选择自动双面打印的解决方法
  14. 保研之路——北邮网研院交换中心夏令营
  15. CLIP:多模态领域革命者
  16. python 爬虫-养生之道
  17. 计算机机房的网络属于,学校机房的网络属于()。
  18. LK源码解析 9 总结
  19. 分享!手机浏览器跳转微信一键添加微信好友或一键关注公众号的方案
  20. 住房月租金预测大数据赛

热门文章

  1. FFmpeg+Python打造命令行工具箱
  2. 牛客网输入输出-python
  3. 流体工厂车间自动化控制、机电管道3d设计优化
  4. CF单机版终极猎手30人版安装教程
  5. 新能源领域中的风电滑环
  6. web前端开发企业命名规范
  7. MLA Review之二:决策树
  8. 光盘镜像,如何打开光盘镜像文件?
  9. 传奇私服脚本解密器的研究
  10. 计算机显存影响什么,笔记本独立显存是什么意思(电脑误区:显存越大,性能就越好)...