总结Go语言中defer的使用和注意要点
前言
defer是golang语言中的关键字,用于资源的释放,会在函数返回之前进行调用。
一般采用如下模式:
f,err:=os.Open(filename)
iferr!=nil{
panic(err)
}
deferf.Close()
如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。
延时调用函数的语法如下:
deferfunc_name(param-list)
当一个函数调用前有关键字defer时,那么这个函数的执行会推迟到包含这个defer语句的函数即将返回前才执行.例如:
funcmain(){
deferfmt.Println("Fourth")
fmt.Println("First")
fmt.Println("Third")
}
最后打印顺序如下:
First Second Third
需要注意的是,defer调用的函数参数的值defer被定义时就确定了.
例如:
i:=1
deferfmt.Println("Deferredprint:",i)
i++
fmt.Println("Normalprint:",i)
打印的内容如下:
Normalprint:2 Deferredprint:1
因此我们知道,在"deferfmt.Println("Deferredprint:",i)"调用时,i的值已经确定了,因此相当于deferfmt.Println("Deferredprint:",1)了.
需要强调的时,defer调用的函数参数的值在defer定义时就确定了,而defer函数内部所使用的变量的值需要在这个函数运行时才确定.
例如:
funcf1()(rint){
r=1
deferfunc(){
r++
fmt.Println(r)
}()
r=2
return
}
funcmain(){
f1()
}
上面的例子中,最终打印的内容是"3",这是因为在"r=2"赋值之后,执行了defer函数,因此在这个函数内,r的值是2了,自增后变为3.
defer顺序
如果有多个defer调用,则调用的顺序是先进后出的顺序,类似于入栈出栈一样:
funcmain(){
deferfmt.Println(1)
deferfmt.Println(2)
deferfmt.Println(3)
deferfmt.Println(4)
}
最先执行的是"fmt.Println(4)",接着是"fmt.Println(3)"依次类推,最后的输出如下:
4 3 2 1
defer注意要点
defer函数调用的执行时机是外层函数设置返回值之后,并且在即将返回之前.
例如:
funcf1()(rint){
deferfunc(){
r++
}()
return0
}
funcmain(){
fmt.Println(f1())
}
上面fmt.Println(f1())打印的是什么呢?很多朋友可能会认为打印的是0,但是正确答案是1.这是为什么呢?
要弄明白这个问题,我们需要牢记两点
1、defer函数调用的执行时机是外层函数设置返回值之后,并且在即将返回之前
2、returnXXX操作并不是原子的.
我们将上面的例子改写一下大家就很明白了:
funcf1()(rint){
deferfunc(){
r++
}()
r=0
return
}
当进行赋值操作"r=0"后,才调用defer函数,最后才是返回语句.
因此上面的代码等效于:
funcf1()(rint){
r=0
func(){
r++
}()
return
}
接下来我们再来看一个更有意思的例子:
funcdouble(xint)int{
returnx+x
}
functriple(xint)(rint){
deferfunc(){
r+=x
}()
returndouble(x)
}
funcmain(){
fmt.Println(triple(3))
}
如果我们已经理解了上面所说的内容的话,那么triple函数就很好理解了,它实际上是:
functriple(xint)(rint){
r=double(x)
func(){
r+=x
}()
return
}
defer表达式的使用场景
defer通常用于open/close,connect/disconnect,lock/unlock等这些成对的操作,来保证在任何情况下资源都被正确释放.在这个角度来说,defer操作和Java中的try...finally语句块有异曲同工之处.
例如:
varmutexsync.Mutex
varcount=0
funcincrement(){
mutex.Lock()
defermutex.Unlock()
count++
}
在increment函数中,我们为了避免竞态条件的出现,而使用了Mutex进行加锁.而在进行并发编程时,加锁了却忘记(或某种情况下unlock没有被执行),往往会造成灾难性的后果.为了在任意情况下,都要保证在加锁操作后,都进行对应的解锁操作,我们可以使用defer调用解锁操作.
总结
以上就是这篇文章的全部内容,希望对大家的学习或者工作带来一定的帮助。如果有疑问大家可以留言交流。