当前位置:   article > 正文

Go 延迟调用 defer 用法详解

go defer func

0a7acb78fc09f271267f2742c3de38a7.png

引子

  1. package counter
  2. import (
  3. "log"
  4. "sync"
  5. )
  6. type Counter struct {
  7. mu *sync.Mutex
  8. Value int
  9. }
  10. func NewCounter(value int) *Counter {
  11. return &Counter{
  12. new(sync.Mutex), 0,
  13. }
  14. }
  15. func (c *Counter) Increment() {
  16. c.mu.Lock()
  17. // defer func
  18. defer func() {
  19. c.mu.Unlock()
  20. log.Printf("mu sync.Mutex Unlocked!")
  21. }()
  22. // safe increment Value
  23. c.Value++
  24. }

概述

defer (延迟调用)是 Go语言中的一个关键字,一般用于释放资源和连接、关闭文件、释放锁等。
和defer类似的有java的finally和C++的析构函数,这些语句一般是一定会执行的(某些特殊情况后文会提到),不过析构函数析构的是对象,而defer后面一般跟函数或方法。

用法详解

1、 多个defer语句,按先进后出的方式执行

  1. package main
  2. import "fmt"
  3. func main() {
  4. var whatever [5]struct{}
  5. for i := range whatever {
  6. defer fmt.Println(i)
  7. }
  8. }

输出:

  1. 4
  2. 3
  3. 2
  4. 1
  5. 0

所有的defer语句会放入栈中,在入栈的时候会进行相关的值拷贝(也就是下面的“对应的参数会实时解析”)。

2、defer声明时,对应的参数会实时解析

简单示例:

  1. package main
  2. import "fmt"
  3. func main() {
  4. i := 1
  5. fmt.Println("i =", i)
  6. defer fmt.Print(i)
  7. }

输出:

  1. i = 1
  2. 1

defer后面的语句最后才会执行,后面会讲当defer存在时return的执行逻辑。

辨析:defer后面跟无参函数、有参函数和方法

  1. package main
  2. import "fmt"
  3. //无返回值函数
  4. func test(a int) {
  5. defer fmt.Println("1、a =", a) // ④ 方法:值传递
  6. defer func(v int) { fmt.Println("2、a =", v)} (a) // ③ 有参函数:值传递
  7. defer func() { fmt.Println("3、a =", a)} () // ② 无参函数:函数调用,此时 a 已经是 2 了,故输出 2
  8. a++ // ① defer 之前的最后一行代码行
  9. }
  10. func main() {
  11. test(1)
  12. }

输出:

  1. 3、a = 2
  2. 2、a = 1
  3. 1、a = 1

解释:
① a++变成2之后,3个defer语句以后声明先执行的顺序执行,
② 无参函数中使用的a现在已经是2了,故输出2。
③ 有参函数中的参数 v,会请求参数,直接把参数代入,所以输出1。
④ 方法中的参数a,直接把参数代入,所以输出1。

3、defer 读取函数返回值(return返回机制)

defer、return、返回值三者的执行逻辑是:

  1. return最先执行,return负责将结果写入返回值中;

  2. 接着defer开始执行一些收尾工作;

  3. 最后函数携带当前返回值(可能和最初的返回值不相同)退出。

当defer语句放在return后面时,不会被执行。

如下:

  1. package main
  2. import "fmt"
  3. func f(i int) int{
  4. return i
  5. defer fmt.Print("i =", i) // 在 return i 语句之后,不会被执行
  6. return i+1 // 不会被执行
  7. }
  8. func main() {
  9. f(1)
  10. }

没有输出,因为 return i 之后函数就已经结束了,不会执行 defer。

(1)无名返回值:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func a(i int) int {
  6. defer func() {
  7. i++
  8. fmt.Println("defer2:", i)
  9. }() // ③ 执行: i = 2
  10. defer func() {
  11. i++
  12. fmt.Println("defer1:", i)
  13. }() // ② 后声明,先执行: i = 1
  14. return i // ① i = 0, 已经完成了返回值的赋值,但是这个时候先不返回; 先去执行 defer.
  15. }
  16. func main() {
  17. var a = a(0)
  18. fmt.Println("a:", a)
  19. }

输出:

  1. defer1: 1
  2. defer2: 2
  3. a: 0

解释说明:

①返回值由变量 i 赋值,相当于 返回值=i=0。
②第二个defer中 i++ , i= 1, 第一个 defer中i++, i = 2,所以最终i的值是2。
③但是返回值已经被赋值了,即使后续修改i也不会影响返回值。所以, 最终函数的返回值 = 0。

(2)有名返回值:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func b() (i int) { // 有名返回值: 此处函数声明, 已经指明了返回值就是 i
  6. defer func() {
  7. i++
  8. fmt.Println("defer2:", i)
  9. }()
  10. defer func() {
  11. i++
  12. fmt.Println("defer1:", i)
  13. }()
  14. return i // 或者直接写成 return
  15. }
  16. func main() {
  17. fmt.Println("return:", b())
  18. }

输出:

  1. defer1: 1
  2. defer2: 2
  3. return: 2

解释:
这里已经指明了返回值就是i,所以后续对i进行修改都相当于在修改返回值,所以最终函数的返回值是2。

(3)函数返回值为地址

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func c() *int {
  6. var i int
  7. defer func() {
  8. i++
  9. fmt.Println("defer2:", i)
  10. }()
  11. defer func() {
  12. i++
  13. fmt.Println("defer1:", i)
  14. }()
  15. return &i
  16. }
  17. func main() {
  18. fmt.Println("return:", *(c()))
  19. }

输出:

  1. defer1: 1
  2. defer2: 2
  3. return: 2

解释:

此时的返回值是一个指针(地址),这个指针 =&i,相当于指向变量i所在的地址,两个defer语句都对 i进行了修改,那么返回值指向的地址的内容也发生了改变,所以最终的返回值是2。

再看一个例子:

  1. func f() (r int) {
  2. defer func(r int) {
  3. r = r + 5
  4. }(r)
  5. return r // 返回值 r
  6. }

最初返回值r的值是1,虽然defer语句中函数的参数名也叫r,但传参的时候是值传递,返回值 r 并没有被修改,最终的返回值仍是1。

4、defer与闭包( ! 容易写出 bug)

  1. package main
  2. import "fmt"
  3. type Test struct {
  4. name string
  5. }
  6. func (t *Test) pp() {
  7. fmt.Println(t.name)
  8. }
  9. func main() {
  10. ts := []Test{{"a"}, {"b"}, {"c"}}
  11. for _, t := range ts {
  12. defer t.pp()
  13. }
  14. }

输出:

  1. c
  2. c
  3. c

解释:

for 结束时 t.name=“c”,接下来执行的那些defer语句中用到的 t.name 的值均为”c“。

修改代码为:

  1. package main
  2. import "fmt"
  3. type Test struct {
  4. name string
  5. }
  6. func pp(t Test) {
  7. fmt.Println(t.name)
  8. }
  9. func main() {
  10. ts := []Test{{"a"}, {"b"}, {"c"}}
  11. for _, t := range ts {
  12. defer pp(t) // 这个故事告诉我们,尽量使用"局部变量"
  13. }
  14. }

输出:

  1. c
  2. b
  3. a

解释:

defer语句中的参数会实时解析,所以在碰到defer语句的时候就把此时的 t 代入了。

再次修改代码:

  1. package main
  2. import "fmt"
  3. type Test struct {
  4. name string
  5. }
  6. func (t *Test) pp() {
  7. fmt.Println(t.name)
  8. }
  9. func main() {
  10. ts := []Test{{"a"}, {"b"}, {"c"}}
  11. for _, t := range ts {
  12. tt := t // 这个故事告诉我们,尽量使用"局部变量"
  13. println(&tt)
  14. defer tt.pp()
  15. }
  16. }

输出:

  1. 0xc000010200
  2. 0xc000010210
  3. 0xc000010220
  4. c
  5. b
  6. a

解释:

① :=用来声明并赋值,连续使用2次a:=1就会报错,但是在for循环内,可以看出每次tt:=t时,tt 的地址都不同,说明他们是不同的变量,所以并不会报错。
② 每次都有一个新的变量tt:=t,所以每次在执行defer语句时,对应的tt不是同一个(for循环中实际上生成了3个不同的tt),所以输出的结果也不相同。

5、defer用于关闭文件和互斥锁

文件

  1. func ReadFile(filename string) ([]byte, error) {
  2. f, err := os.Open(filename)
  3. if err != nil {
  4. return nil, err
  5. }
  6. defer f.close() // finally close the file
  7. return ReadAll()
  8. }

互斥锁

  1. var mu sync.Mutex
  2. var m = make(map[string]int)
  3. func lookup(key string) int {
  4. mu.Lock()
  5. defer mu.Unlock() // 延迟调用 Unlock(), finally
  6. return m[key]
  7. }

6、“解除”对所在函数的依赖

  1. package main
  2. import "fmt"
  3. import "time"
  4. type User struct {
  5. username string
  6. }
  7. func (this *User) Close() {
  8. fmt.Println(this.username, "Closed !!!")
  9. }
  10. func main() {
  11. u1 := &User{"jack"}
  12. defer u1.Close()
  13. u2 := &User{"lily"}
  14. defer u2.Close()
  15. time.Sleep(10 * time.Second)
  16. fmt.Println("Done !")
  17. }

输出:

  1. Done !
  2. lily Closed !!!
  3. jack Closed !!!

解释:
defer后面跟无参函数,u1.Close()和u2.Close()要等 sleep和 fmt.Println(“Done !”)之后才可以执行,也就是在函数最终返回之前执行。

修改代码为:

  1. package main
  2. import "fmt"
  3. import "time"
  4. type User struct {
  5. username string
  6. }
  7. func (this *User) Close() {
  8. fmt.Println(this.username, "Closed !!!")
  9. }
  10. func f(u *User) {
  11. defer u.Close()
  12. }
  13. func main() {
  14. u1 := &User{"jack"}
  15. f(u1)
  16. u2 := &User{"lily"}
  17. func() { defer u2.Close() }()
  18. time.Sleep(10 * time.Second)
  19. fmt.Println("Done !")
  20. }

输出:

  1. jack Closed !!!
  2. lily Closed !!!
  3. Done !

这样的使用方式,似乎不太合理,但却有存在的必要性。大多数情况下,可以用于 u1,u2 之类非常消耗内存,或者cpu,其后执行时间过程且没有太多关联的情况。
既保留了defer的功能特性,也满足范围精确控制的条件 (???)

7、defer与panic

(1)在panic语句后面的defer语句不被执行

  1. func panicDefer() {
  2. panic("panic")
  3. defer fmt.Println("defer after panic") // 不会执行到
  4. }

输出:

  1. panic: panic
  2. goroutine 1 [running]:
  3. main.panicDefer()
  4. E:/godemo/testdefer.go:17 +0x39
  5. main.main()
  6. E:/godemo/testdefer.go:13 +0x20
  7. Process finished with exit code 2

可以看到 defer 语句没有执行。

(2)在panic语句前的defer语句会被执行

  1. func deferPanic() {
  2. defer fmt.Println("defer before panic")
  3. panic("panic")
  4. }

输出:

  1. defer before panic
  2. panic: panic
  3. goroutine 1 [running]:
  4. main.deferPanic()
  5. E:/godemo/testdefer.go:19 +0x95
  6. main.main()
  7. E:/godemo/testdefer.go:14 +0x20
  8. Process finished with exit code 2

defer 语句输出了内容。
Go中的panic类似其它语言中的抛出异常,panic后面的代码不再执行(panic语句前面的defer语句会被执行)。

8、调用os.Exit时defer不会被执行

  1. func deferExit() {
  2. defer func() {
  3. fmt.Println("defer")
  4. }() // ① defer func
  5. os.Exit(0) // 调用 os.Exit(), 不会执行 ① defer func
  6. }

当调用os.Exit()方法退出程序时,defer并不会被执行,上面的defer并不会输出。

参考资料:

http://www.topgoer.com/函数/延迟调用defer.html

https://blog.csdn.net/eclipser1987/article/details/12089271

https://www.cnblogs.com/aiandbigdata/p/10822123.html

https://blog.csdn.net/chr1991/article/details/104771526?utm_medium=distribute.pc_relevant.none-task-blog-title-1&spm=1001.2101.3001.4242

https://www.jianshu.com/p/79c029c0bd58

https://blog.csdn.net/qq_21816375/article/details/78161603

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/675032
推荐阅读
相关标签
  

闽ICP备14008679号