defer是什么

defer是Go语言提供的一种用于注册延迟调用的机制,让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行。

defer与panic和recover结合,形成了go语言风格的异常与捕获机制

常用于一些成对操作的场景:

打开连接/关闭连接;加锁/释放锁;打开文件/关闭文件等。

package mainimport "os"func main() {f, err := os.Open(filename)if err != nil {panic(err)}if f != nil {defer f.Close()}
}

优点:方便使用

缺点:有性能损耗

每次defer语句执行时,会把函数“压栈”,函数参数会被拷贝下来

当外层函数(非代码块,eg一个for循环)退出时,defer函数按照定义的逆序执行

如果defer执行的函数为nil,那么会在最终调用函数产生panic

defer参数

在defer函数定义时,对外部变量的引用是有两种方式的

1.作为函数参数

在defer定义时就把值传递给defer,并被cache起来

2. 作为闭包引用

在defer函数真正调用时根据整个上下文确定当前的值

闭包

闭包=函数+引用环境

go的所有匿名函数都是闭包

package mainimport "fmt"func main() {var a = Accumulator()fmt.Printf("%d\n", a(1))fmt.Printf("%d\n", a(10))fmt.Printf("%d\n", a(100))fmt.Println("------------------------")var b = Accumulator()fmt.Printf("%d\n", b(1))fmt.Printf("%d\n", b(10))fmt.Printf("%d\n", b(100))
}
func Accumulator() func(int) int {var x intreturn func(delta int) int {fmt.Printf("(%+v,%+v) - ", &x, x)x += deltareturn x}
}

运行结果

(0xc000016098,0) - 1
(0xc000016098,1) - 11
(0xc000016098,11) - 111
------------------------
(0xc0000160f0,0) - 1
(0xc0000160f0,1) - 11
(0xc0000160f0,11) - 111

闭包引用了x变量,a,b可看作2个不同的实例,实例之间互不影响。实例内部,x变量是同一个地址,因此具有“累加效应”。

defer易错场景

type number
int func (n number)print(){fmt.Println(n)
}func (n*number)pprint(){fmt.Println(*n)
}func main(){var n numberdefer n.print()defer n.pprint()defer func(){n.print()}()defer func (){n.pprint()}()n = 3}

第四个defer语句是闭包,引用外部函数的n, 最终结果是3;

第三个defer语句同第四个;

第二个defer语句,n是引用,最终求值是3.

第一个defer语句,对n直接求值,开始的时候n=0, 所以最后是0;

defer拆解

return xxx

经过编译后变成三条指令

  1. 返回值 = xxx

  2. 调用defer函数

  3. 空的return

例一

package mainimport "fmt"func f() (r int) {t := 5defer func() {t = t + 5}()return t
}
func main() {fmt.Println(f())//5
}

拆解后

package mainimport "fmt"func f() (r int) {t := 5//1.赋值指令r = t//2.defer被插入到赋值与返回之间执行,返回值r未被修改过func() {t = t + 5}()//3.空的return指令return
}
func main() {fmt.Println(f())
}

例二

package mainimport "fmt"func f() (r int) {t := 5//1.赋值指令//2.这里改的t是之前传值传进去的t,不会改变要返回的tdefer func(t int) {t = t + 5}(t)//3.空的return指令return t
}
func main() {fmt.Println(f())//5
}
package mainimport ("errors""fmt"
)func f1() {var err errordefer fmt.Println(err)err = errors.New("defer error")return
}
func f2() {var err errordefer func() {fmt.Println(err)}()err = errors.New("defer error")return
}
func f3() {var err errordefer func(err error) {fmt.Println(err)}(err)err = errors.New("defer error")return
}
func main() {f1()f2()f3()
}

运行结果:

<nil>
defer error
<nil>  

执行顺序 1->2->3

第1,3个函数是因为作为函数参数,定义的时候就会求值,定义的时候err变量的值都是nil, 所以最后打印的时候都是nil.

第2个函数的参数其实也是会在定义的时候求值,只不过,第2个例子中是一个闭包,它引用的变量err在执行的时候最终变成 defer error

defer配合recover

一次偶然的请求可能会触发某个bug, 这时用recover捕获panic, 稳住主流程,不影响其他请求。

recover()函数只在defer的上下文中才有效(且只有通过在defer中用匿名函数调用才有效),直接调用的话,只会返回 nil.

package mainimport ("fmt""os""time"
)func main() {defer fmt.Println("defer main")var user = os.Getenv("USER_")go func() {defer func() {fmt.Println("defer caller")if err := recover(); err != nil {fmt.Println("recover success. err", err)}}()func() {defer func() {fmt.Println("defer here")}()if user == "" {panic("should set user env.")}//此处不会执行fmt.Println("after panic")}()}()time.Sleep(100)fmt.Println("end of main function")
}

输出

defer here
defer caller
recover success. err should set user env.
end of main function
defer main 

注意:

panic后的defer不会被执行(遇到panic,如果没有捕获错误,函数会立即终止)

panic没有recover时,抛出的panic到当前goroutine最上层函数时,最上层程序直接异常终止

package mainimport "fmt"func F() {defer func() {fmt.Println("b")}()panic("a")
}
func main() {defer func() {fmt.Println("c")}()F()fmt.Println("继续执行")
}
/*
b
c
panic: a
*/

panic有被recover时,当前goroutine最上层函数正常执行

package mainimport "fmt"func F() {defer func() {if err := recover(); err != nil {fmt.Println("捕获异常", err)}fmt.Println("b")}()panic("a")
}
func main() {defer func() {fmt.Println("c")}()F()fmt.Println("继续执行")
}
/*
捕获异常 a
b
继续执行
c
*/

golang——defer相关推荐

  1. golang defer实现

    golang defer实现 1.现象 defer 会在函数return前执行 如果发生非系统级别panic,defer依然执行:在defer执行过程中,如果有recover,那就捕获panic,否则 ...

  2. golang defer简介 goland 警告提示 possible resource leak,difer is called in a for loop 原因

    目录 警告原因 解决方法 defer理解 defer调用是一个栈结构 defer的作用域是一个函数,不是一个语句块 链式调用 针对非指针类型调用函数 警告原因 在for中使用defer关闭资源,其实资 ...

  3. golang defer的使用

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

  4. Golang defer 快速上手

    文章目录 1.简介 2.注意事项 2.1 defer 函数入参在 defer 时确定 2.2 defer 执行顺序为后进先出 2.3 defer 函数在 return 语句赋值与返回之间执行 2.4 ...

  5. Golang defer

    在Golang使用defer常常会迷惑于以下两个问题 defer 关键字的调用时机以及多次调用 defer 时执行顺序是如何确定的: defer 关键字使用传值的方式传递参数时会进行预计算,导致不符合 ...

  6. Golang defer解读

    defer defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer语句通常用于一些成对 ...

  7. golang defer 关闭文件 报错file may have nil or other unexpected value as its corresponding error

    错误实例: file, err := os.Open("xxx.txt") defer file.Close() if err != nil {return err } 初学者很多 ...

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

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

  9. [Golang]defer详解

    数据结构 defer的数据结构定义在$GOROOT/src/runtime/runtime2.go // 大体定义如下,忽略少部分字段 type _defer struct {sp uintptr / ...

最新文章

  1. L - JavaScript继承
  2. 遥望星空FTP文件同步工具(附源码)1.0 发布
  3. 关于JVM和Dalvik的区别
  4. 关于memecache的使用及清楚示意
  5. d.php xfso_centos平台基于snort、barnyard2以及base的IDS(入侵检测系统)的搭建与测试及所遇问题汇总...
  6. Vue 左右翻页,点赞动画
  7. Spark的相关概念说明和检查点机制
  8. ltp-ddt realtime_cpu_load涉及的cyclictest 交叉编译
  9. VB 获得磁盘的文件系统
  10. 给出如下公式的python表达式7+9i+2xcos66_这100道练习,带你玩转Numpy
  11. Android学习第六天---seekbar
  12. Python之路 - 网络编程之Socket
  13. 如何从wireshark 抓包中的RTP导出 H.264 PAYLOAD,变成可用暴风直接播放的H264 裸码流文件
  14. 华硕主板装系统蓝屏_华硕笔记本电脑重装系统后蓝屏怎么办
  15. localhost改成想要的IP方法
  16. 带省略号的比喻句_标点符号往往能引发人们的联想,例如:“省略号像一条漫长的人生道路,等着你去书写它留下的空白。”请以一种标点符号(省略号除外)为描述对象,写一个比喻句,形象地阐发某种生活道理。...
  17. EN300328测试软件,蓝牙耳机EN300328测试项目。
  18. 计算机网络考试部分题库
  19. Kotlin全套视频教程分享
  20. Python爬虫的起点,一文轻松入门

热门文章

  1. 西门子快出手 华为慢接招
  2. iOS 7完美越狱发布 中国版存“猫腻”
  3. 中文分词算法及python代码实现(持续更新中)
  4. 牛客网算法——名企高频面试题143题(2)
  5. 029 怎样防止高考报考被骗
  6. SecureCRT输入命令没有本地回显问题
  7. win10系统可以做补丁服务器,win10补丁包怎么安装 win10补丁手动安装方法
  8. “我爱智能”原创性博客索引
  9. 线性规划问题的数学建模matlab,数学建模讲座之三——利用Matlab求解线性规划问题(linprog函数).ppt...
  10. H3C 交换机软件版本升级详细步骤