延迟语句是什么
编程的时候,经常需要申请一些资源,比如数据库连接、文件、锁等,这些资源需要在用完之后释放掉,否则会造成内存泄漏。但编程人员经常容易忘记释放这些资源,从而造成一些事故。Go 语言直接在语言层面提供 defer 关键字,在申请资源语句的下一行,可以直接用 defer 语句来注册函数结束后执行释放资源的操作。因为这样一颗小小的语法糖,忘写关闭资源语句的情况就大大地减少了。
defer 是 Go 语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过 return 正常结束或者 panic 导致的异常结束)执行。在需要释放资源的场景非常有用,可以很方便地在函数结束前做一些清理操作。在打开资源语句的下一行,直接使用 defer 就可以在函数返回前释放资源,可谓相当有效。
defer 通常用于一些成对操作的场景:打开连接 / 关闭连接、加锁 / 释放锁、打开文件 / 关闭文件等。使用非常简单:
f,err := os.Open(filename)
if err != nil {
panic(err)
}
if f != nil {
defer f.Close()
}
2
3
4
5
6
7
在打开文件的语句附近,用 defer 语句关闭文件。这样,在函数结束之前,会自动执行 defer 后面的语句来关闭文件。注意,要先判断 f 是否为空,如果 f 不为空,再调用 f.Close () 函数,避免出现异常情况。
当然,defer 会有短暂延迟,对时间要求特别高的程序,可以避免使用它,其他情况一般可以忽略它带来的延迟。特别是 Go 1.14 又对 defer 做了很大幅度的优化,效率提升了不少。
我们以一个反面例子来结束这一节:
r.mu.Lock()
rand.Intn(param)
r.mu.Unlock()
2
3
上面只有三行代码,看起来这里不用 defer 执行 Unlock 并没有什么问题。其实并不是这样, 中间这行代码 rand.Intn (param) 其实是有可能发生 panic 的,更严重的情况是,这段代码很有可能被其他人修改,增加更多的逻辑,而这完全不可控。也就是说,在 Lock 和 Unlock 之间的代码一旦出现异常情况导致 panic,就会形成死锁。因此这里的逻辑是,即使是看起来非常简单的代码,使用 defer 也是有必要的,因为需求总是在变化,代码也总会被修改。