defer 函数
关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 return 语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)。
(译者注:return 是非原子性的,需要两步,执行前首先要得到返回值 (为返回值赋值),return 将返回值返回调用处。
defer 和 return 的执行顺序是先为返回值赋值,然后执行 defer,然后 return 到函数调用处。
关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源。
在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制,只要函数执行完毕之后,那么它一定会将你指定的资源关闭)。
func TestDefer(t *testing.T) {
defer func() { //后面可以是命名函数,或者匿名函数,总之是函数调用
t.Log("Clear resources")
}()
t.Log("Started")
panic("Fatal error”)
//defer仍会执⾏,如果是其他语句放在Panic之后,那么这行语句是不可达的,
//也就是Panic后面的语句是不可执行的
}
defer的函数调用不会立刻执行,而是会执行Panic语句,执行完了在函数返回前才会执行defer的函数,依据这种特性,可能会想到在java里面的try finally的结构,这个类似于finally,最后返回时候执行的。
这有什么用呢,通常都是清理某些资源,释放某些锁,不管是报了异常还是异常退出了,都不会忘记将我们的锁/资源释放掉,以免导致资源的浪费,导致程序被锁住。
这里抛出了Panic,这里是程序异常中断,在go里面代表不可修复的错误。即便是在Panic的情况下,defer后面的函数也是会执行的。
当代码执行到defer到时候,暂时不会执行,而是会压入到独立defer的栈中,和函数栈不在同一个地方。当函数执行完毕之后再从defer栈中按照先入后出的方式执行(其实就是return过后,或者没有return的函数执行完毕之前,再去defer栈中取数据)。
defer 关键字用户声明函数,不论函数是否发生错误都在函数执行最后执行(return 之前),若使用 defer 声明多个函数,则按照声明的顺序,先声明后执行(堆) 常用来做资源释放,记录日志等工作。
其实就是将函数执行完毕要执行的语句先压入到defer栈当中,等到函数执行完毕之后依次的执行。defer后面一般是加匿名函数的调用!
fmt.Println("start")
defer func() {
fmt.Println("defer")
}()
fmt.Println("end")
start
end
defer
defer的作用就是延时执行,defer延时到什么时候呢,延时到函数退出之前执行
fmt.Println("start")
defer func() {
fmt.Println("defer1")
}()
defer func() {
fmt.Println("defer2")
}()
fmt.Println("end")
start
end
defer2
defer1
可以看到是堆栈的模式,谁先声明,最后执行。
将defer语句放到栈时,也会将相关的值拷贝入栈。当时的值是多少就入栈是多少,就算变量在后面发生了变化,依然不会有影响!!!!!!!!!!!!!!!!!!!!!
举例
是否可以有多个 defer,如果可以的话,它们的执行顺序是怎么样的?对于这道题,可以直接采用写代码测试的方式,如下所示:
func moreDefer(){
defer fmt.Println("First defer")
defer fmt.Println("Second defer")
defer fmt.Println("Three defer")
fmt.Println("函数自身代码")
}
func main(){
moreDefer()
}
我定义了 moreDefer 函数,函数里有三个 defer 语句,然后在 main 函数里调用它。运行这段程序可以看到如下内容输出:
函数自身代码
Three defer
Second defer
First defer
通过以上示例可以证明:
-
在一个方法或者函数中,可以有多个 defer 语句;
-
多个 defer 语句的执行顺序依照后进先出的原则。
defer 有一个调用栈,越早定义越靠近栈的底部,越晚定义越靠近栈的顶部,在执行这些 defer 语句的时候,会先从栈顶弹出一个 defer 然后执行它,也就是我们示例中的结果。
defer的最佳实践
defer最主要的价值是在,当函数执行完毕后,可以及时的释放函数到建的资源,看下模拟代码。
1) 在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源), 可以执行defer file.Close() defer connect.Close()
2) 在defer后,可以继续使用创建资源
3) 当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源,这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心
defer 函数
在一个自定义函数中,你打开了一个文件,然后需要关闭它以释放资源。不管你的代码执行了多少分支,是否出现了错误,文件是一定要关闭的,这样才能保证资源的释放。
如果这个事情由开发人员来做,随着业务逻辑的复杂会变得非常麻烦,而且还有可能会忘记关闭。基于这种情况,Go 语言为我们提供了 defer 函数,可以保证文件关闭后一定会被执行,不管你自定义的函数出现异常还是错误。
延时执行有个关键字defer,defer后面跟着的是函数调用,函数调用就是要加括号的。
下面的代码是 Go 语言标准包 ioutil 中的 ReadFile 函数,它需要打开一个文件,然后通过 defer 关键字确保在 ReadFile 函数执行结束后,f.Close() 方法被执行,这样文件的资源才一定会释放。
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
//省略无关代码
return readAll(f, n)
}
defer 关键字用于修饰一个函数或者方法,使得该函数或者方法在返回前才会执行,也就说被延迟,但又可以保证一定会执行。
以上面的 ReadFile 函数为例,被 defer 修饰的 f.Close 方法延迟执行,也就是说会先执行 readAll(f, n),然后在整个 ReadFile 函数 return 之前执行 f.Close 方法。
defer 语句常被用于成对的操作,如文件的打开和关闭,加锁和释放锁,连接的建立和断开等。不管多么复杂的操作,都可以保证资源被正确地释放。