在golang中弱化了异常机制,defer是一种finally的实现机制,它的工作原理是这样的
它加在一个函数调用前面,当执行到这一行时,它会计算参数表达式的值,但并不立刻执行函数调用;
当函数结束运行时,开始执行defer的函数调用;
如果在一个函数中出现了多个defer语句,那么当函数运行结束时,按照出栈顺序来执行它们;
这样做的好处是,我们仿佛可以将finally语句块中的代码段,以更小的粒度、一行一行地穿插到主逻辑的不同位置,并且事后按照当时的参数去调用。
func accumulate(counterPtr *int) {
for i := 0; i < 5; i++ {
*counterPtr++
defer fmt.Println(*counterPtr)
}
fmt.Printf("counter = %d \n", *counterPtr)
}
func main() {
var couter = 0
accumulate(&couter)
}
输出结果是:
counter = 5
5
4
3
2
1
它跟finally有些相似,主要被用于资源相关的成对的操作,比如文件的打开和关闭,连接的建立和断开,加锁和解锁等,并且它的特点是,一旦打开的操作成功了,马上写带defer的关闭操作,这样就可以完美解决资源释放的问题。
fund ReadFile(filename string)([]byte, error){
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close() // 一旦成功打开,马上defer关闭,看上去怪怪的,还没读呢怎么就关闭了呢。对,是这样的
return ReadAll(f)
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() { log.Printf("exit %s (%s)", msg, time.Since(start)) }
}
上面的代码的返回值一个带有闭包的匿名函数,闭包中的内容就是开始时间。
defer trace("bigSlowOperation")() // 注意后面的第二个括号(第一个括号返回了一个匿名函数,第二个括号是调用它)
time.Sleep(10 * time.Second) // 这一行运行完毕之后,当前函数结束,开始执行defer后面的函数
输出结果是
2021/11/05 16:36:20 enter bigSlowOperation
2021/11/05 16:36:30 exit bigSlowOperation (10.003480853s)
为什么会先执行trace函数呢,因为defer语句会先执行对应的参数表达式,留下一个待执行的函数,trace函数算是对应的表达式,而trace函数返回的那个匿名函数才是defer的真正函数。
func double(x int) (result int) {
defer func() { fmt.Printf("double (%d) = %d\n", x, result) }()
return x + x
}
上面的double
函数定义并执行了一个匿名函数,但加了defer关键字,也就意味着这个匿名函数在外层的double函数返回以后再执行,以此来记录函数结束后的结果。
_ = double(4) // 输出 double (4) = 8
当然,可以记录返回结果,就可以修改它,例如
func triple(x int) (result int) {
defer func() { result += x }()
return double(x)
}
fmt.Println(triple(4)) // 输出 12
上面是同样的套路,在结束时给result重新赋了个值。
如果使用finally的话,就可以控制收尾的语句的执行时间,但如果使用defer的话,就完全依赖当前scope何时运行结束,如果在一个循环中打开资源并defer关闭的话,可能会关闭很晚。解决办法是将资源的打开和关闭封装为一个函数,在循环中循环调用,这样做就增加了一个开始和结束scope的机会,这样可以及时触发defer函数运行。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://laozhu.blog.csdn.net/article/details/121243084
内容来源于网络,如有侵权,请联系作者删除!