golang中的defer

x33g5p2x  于2021-11-12 转载在 Go  
字(1.8k)|赞(0)|评价(0)|浏览(339)

在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函数运行。

相关文章