1. 使用defer的优势
defer专注app软件定制开发一般用于资源的释放和专注app软件定制开发异常的捕捉, 专注app软件定制开发作为的特性之一.
defer 专注app软件定制开发语句会将其后面跟随的语句进行延迟处理. 意思就是说 跟在defer后面的语言 将会在程序进行最后的return之后再执行.
在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
1.1 资源的释放
一般我们写读取文件的代码如下
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } dst, err := os.Create(dstName) if err != nil { return } dst.Close() src.Close() return}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在程序最开始,os.Open及os.Create打开了两个文件资源描述符,并在最后通过file.Close方法得到释放,在正常情况下,该程序能正常运行,一旦在dstName文件创建过程中出现错误,程序就直接返回,src资源将得不到释放。因此需要在所有错误退出时释放资源,即修改为如下代码才能保证其在异常情况下的正确性。
即在每个err里面如果发生了异常, 要及时关闭src的资源.
这个问题出现在加锁中也非常常见
l.lock()// 如果下面发生了异常// 我们需要在每个err处理块中都加入l.unlock()来解锁// 不然资源就得不到释放, 就会产生死锁if err != nil { l.unlock() return}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
但是这样做未免太麻烦了, defer优雅的帮我们解决了这个问题
比如我们可以这样
src, err := os.Open(srcName) defer src.Close() if err != nil { return } dst, err := os.Create(dstName) defer dst.Close() if err != nil { return } ------------------------------------------ l.lock() defer l.unlock() ...... if err != nil { return } ......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这样写的话, 就不需要在每个异常处理块中都加上Close() 或者 unlock()语句了
1.2 异常的捕捉
程序在运行时可能在任意的地方发生panic异常,例如算术除0错误、内存无效访问、数组越界等,这些错误会导致程序异常退出。在很多时候,我们希望能够捕获这样的错误,同时希望程序能够继续正常执行。一些语言采用try…catch语法,当try块中发生异常时,可以通过catch块捕获。
Go语言使用了特别的方式处理这一问题。defer的特性是无论后续函数的执行路径如何以及是否发生了panic,在函数结束后一定会得到执行,这为异常捕获提供了很好的时机。异常捕获通常结合recover函数一起使用。
如上所示,在executePanic函数中,手动执行panic函数触发了异常。当异常触发后,函数仍然会调用defer中的函数,然后异常退出。输出如下,表明调用了defer中的函数,并且main函数将不能正常运行,程序异常退出打印出栈追踪信息。
如下所示,当在defer函数中使用recover进行异常捕获后,程序将不会异常退出,并且能够执行正常的函数流程。如下输出表明,尽管有panic,main函数仍然在正常执行后退出。
使用了recover函数后, 程序将不会异常退出, 仍会正常执行
2. 多个defer语句的执行顺序
当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出), 相当于开辟了一个延时调用栈
func main() { fmt.Println("defer begin") // 将defer放入延迟调用栈 defer fmt.Println(1) defer fmt.Println(2) // 最后一个放入, 位于栈顶, 最先调用 defer fmt.Println(3) fmt.Println("defer end")}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
执行的结果就是
// 先打印正常语句defer begindefer end// 然后按从上到下的顺序执行defer调用栈中的语句321
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8