golang-defer
defer的使用特点
其实其中一点特性我理解起来就有点像java中的finally的用法
关于官方解释
1 | A defer statement defers the execution of a function until the surrounding function returns. |
这里提到了defer调用的参数会立即计算,但在周围函数返回之前不会执行函数调用。
以及延迟函数调用被压入堆栈。当函数返回时,其延迟调用以后进先出顺序执行。
它有如何特点
- 所在的函数中,它在 return 或 panic 或 执行完毕 后被调用
- 多个 defer,它们的被调用顺序,为栈的形式。先进后出,先定义的后被调用
看下面几个例子:
- 在计算defer语句时,将计算延迟函数的参数。在此示例中,在延迟Println调用时计算表达式“i”。函数返回后,延迟调用将打印“0”。
1 | func a() { |
- 在周围函数返回后,延迟函数调用以后进先出顺序执行。
1 | func b() { |
然后不免在使用过程中会遇到这些坑
坑1. defer在匿名返回值和命名返回值函数中的不同表现
1 | func returnValues() int { |
上面的方法会输出0,下面的方法输出1。上面的方法使用了匿名返回值,下面的使用了命名返回值,除此之外其他的逻辑均相同,为什么输出的结果会有区别呢?
要搞清这个问题首先需要了解defer的执行逻辑,defer语句在方法返回“时”触发,也就是说return和defer是“同时”执行的。以匿名返回值方法举例,过程如下。
- 将result赋值给返回值(可以理解成Go自动创建了一个返回值retValue,相当于执行retValue = result)
- 然后检查是否有defer,如果有则执行
- 返回刚才创建的返回值(retValue)
在这种情况下,defer中的修改是对result执行的,而不是retValue,所以defer返回的依然是retValue。在命名返回值方法中,由于返回值在方法定义时已经被定义,所以没有创建retValue的过程,result就是retValue,defer对于result的修改也会被直接返回。
坑2. 判断执行没有err之后,再defer释放资源
一些获取资源的操作可能会返回err参数,我们可以选择忽略返回的err参数,但是如果要使用defer进行延迟释放的的话,需要在使用defer之前先判断是否存在err,如果资源没有获取成功,即没有必要也不应该再对资源执行释放操作。如果不判断获取资源是否成功就执行释放操作的话,还有可能导致释放方法执行错误。
正确做法
1 | resp, err := http.Get(url) |
坑3. 调用os.Exit时defer不会被执行
当发生panic时,所在goroutine的所有defer会被执行,但是当调用os.Exit()方法退出程序时,defer并不会被执行。
1 | func deferExit() { |
上面的defer并不会输出。
坑4.非引用传参给defer调用的函数,且为非闭包函数,值不会受后面的改变影响
1 | func defer0() { |
坑5. 传递引用给defer调用的函数,即使不使用闭包函数,值也会受后面的改变影响
1 | func myPrintln(point *int) { |
坑6. 传递值给defer调用的函数,且非闭包函数,值不会受后面的改变影响
1 | func p(a int) { |
坑7. defer调用闭包函数,且内调用外部非传参进来的变量,值会受后面的改变影响
1 | // 闭包函数内,事实是该值的引用 |
坑8. defer调用闭包函数,若内部使用了传参参数的值。使用的是值
1 | func defer5() { |
坑9. defer所调用的非闭包函数,参数如果是函数,会按顺序先执行(函数参数)
1 | func calc(index string, a, b int) int { |
注意
- defer 不影响 return的值
golang-defer