本文内容纲要:
《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 }
运行结果如下
三. 使用场景
-
常用于成对的操作,比如打开和关闭,连接和断开,加锁和解锁。 使用方式,在成功获得资源之后,使用 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 }
- 调试一个复杂的函数
- 书中示例代码如下,开始我没太理解,看了几遍,再自己敲敲代码,再看看书,然后一下子反应过来了。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 }
运行结果如下
- 再次验证一下。去掉上面示例代码中,第11行中的 defer 关键字。可以先自行想下结果
bigSlowOperation 函数执行时,调用了 trace 函数返回的匿名函数,这个过程中呢,先执行了 trace 函数中的内容(打印 enter 字样),然后执行其返回的匿名函数(打印了 exit 字样)。
运行结果如下
- 然后,再次验证一下。在上面的示例代码基础上,在第11行加上 defer 关键字,去掉最后面的 (),可自行想下结果。
这时是延迟调用了 trace 函数了,就不是延迟调用其返回的匿名函数了。trace 函数在 bigSlowOperation 函数结束后才执行,仅仅打印了 enter 字样。trace 函数返回的匿名函数因为没有调用,所以永远不会执行。
运行结果如下,符合预期。
四. 其它
示例
-
初始情况
28 func double(x int) int { 29 return x + x 30 }
-
调整
通过命名结果变量和增加 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 }
调用上面的函数,打印结果如下
- 再调整,增加一个函数 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 }
运行结果如下
本文内容总结: