本文内容纲要:

《Go程序设计语言》学习笔记之defer

一. 环境

  Centos8.5, go1.17.5 linux/amd64

二. 概念

语法上,一个 defer 语句就是一个普通的函数或方法调用,在调用之前加上关键字 defer 。

执行时机

无论是正常情况下,如执行 return 或函数执行完毕,还是不正常的情况下,比如发生宕机,实际的调用推迟到包含 defer 语句的函数结束后才执行。

defer 语句没有限制使用次数

执行的时候以调用 defer 语句顺序的倒序进行

23 func main() {
 24     defer fmt.Println("a")
 25     defer fmt.Println("b")
 26     defer fmt.Println("c")
 27     fmt.Println("----------")
 28 }

  运行结果如下

Image

三. 使用场景

  1. 常用于成对的操作,比如打开和关闭,连接和断开,加锁和解锁。 使用方式,在成功获得资源之后,使用 defer 语句。

    27 resp, err := http.Get(url) 28 if err != nil { 29 return err 30 } 31 defer resp.Body.Close() 32----------------------------- 33 f, err := os.Open(filename) 34 if err != nil { 35 return nil, err 36 } 37 defer f.Close() 38 ---------------------------- 39 var mu sync.Mutex 40 var m = make(map[string]int) 41 func lookup(key string) int { 42 mu.Lock() 43 defer mu.Unlock() 44 return m[key] 45 }

  

  1. 调试一个复杂的函数
  1. 书中示例代码如下,开始我没太理解,看了几遍,再自己敲敲代码,再看看书,然后一下子反应过来了。bigSlowOperation函数有一个复杂中的操作,代码中以 第13行代码模拟了费时的操作。在bigSlowOperation函数的“入口”和“出口”处设置调试行为。下面是通过延迟调用 trace 函数来实现的,哦,不,这个说法不对,是延迟调用 trace 函数返回的匿名函数来实现的。这里需要注意一下第13行代码中最后面有一个小括号,这里表示是对trace函数返回的匿名函数的调用,而 defer 关键字则来修饰它的。

bigSlowOperation 函数执行时,trace 函数中的代码 第17行、18行正常执行,通过结果可以看到打印了进入 bigSlowOperation 函数时的时间。而匿名函数的调用则延迟到了 bigSlowOperation 函数结束。通过结果,可以看到,停顿了3秒后,打印了结束的时间及 exit 字样。

10 func bigSlowOperation() {
 11     defer trace("bigSlowOperation")()
 12     fmt.Println("----------")
 13     time.Sleep(3 * time.Second)
 14 }
 15
 16 func trace(msg string) func() {
 17     start := time.Now()
 18     log.Printf("enter %s\n", msg)
 19     return func() { log.Printf("exit %s, (%s)\n", msg, time.Since(start)) }
 20
 21 }
 22
 23 func main() {
 24     bigSlowOperation()
 25 }

  运行结果如下

Image

  1. 再次验证一下。去掉上面示例代码中,第11行中的 defer 关键字。可以先自行想下结果

bigSlowOperation 函数执行时,调用了 trace 函数返回的匿名函数,这个过程中呢,先执行了 trace 函数中的内容(打印 enter 字样),然后执行其返回的匿名函数(打印了 exit 字样)。

  运行结果如下

Image

  1. 然后,再次验证一下。在上面的示例代码基础上,在第11行加上 defer 关键字,去掉最后面的 (),可自行想下结果。

这时是延迟调用了 trace 函数了,就不是延迟调用其返回的匿名函数了。trace 函数在 bigSlowOperation 函数结束后才执行,仅仅打印了 enter 字样。trace 函数返回的匿名函数因为没有调用,所以永远不会执行。

  运行结果如下,符合预期。

Image

四. 其它

示例

  1. 初始情况

    28 func double(x int) int { 29 return x + x 30 }

  2. 调整

通过命名结果变量和增加 defer 语句,在每次调用函数的时候输出它的参数和结果。也就是说,在double 函数结束时,执行了匿名函数的调用,打印了参数和结果。

32 func double(x int) (result int) {
 33     defer func() { fmt.Printf("double(%d) = %d\n", x, result) }()
 34     return x + x
 35 }

  调用上面的函数,打印结果如下

Image

  1. 再调整,增加一个函数 triple,并打印其返回结果。

延迟执行的匿名函数可以改变外层函数返回给调用者的结果。在函数 triple 的 result 返回前,延迟调用的匿名函数 func() 修改了 result 的值,于是函数 triple 返回值为12(8 + 4)。

23 func main() {
 24     //double(4)
 25     fmt.Println(triple(4))
 26 }
 27
 28 func double2(x int) int {
 29     return x + x
 30 }
 31
 32 func double(x int) (result int) {
 33     defer func() { fmt.Printf("double(%d) = %d\n", x, result) }()
 34     return x + x
 35 }
 36
 37 func triple(x int) (result int) {
 38     defer func() { result += x }()
 39     return double(x)
 40 }

  运行结果如下

Image

本文内容总结: