defer特性

defer 是在 return 之后执行标记代码行, 直观一点就是函数从上往下一行行执行代码, 遇到defer跳过, return之后, 再从下往上依次执行refer

举一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"log"
	"time"
)

func main() {
	test()
}

func test() {
	for i := 0; i < 5; i++ {
		defer func(index int) {
			log.Printf("i: %d, index: %d \n", i, index)
		}(i)
	}
}

输出:

1
2
3
4
5
2019/12/18 17:11:03 i: 5, index: 4 
2019/12/18 17:11:03 i: 5, index: 3 
2019/12/18 17:11:03 i: 5, index: 2 
2019/12/18 17:11:03 i: 5, index: 1 
2019/12/18 17:11:03 i: 5, index: 0

i是外部变量, 在执行defer的时候, 已经运行完毕for循环, 那么i此时的值就一直是5了

index是函数内部变量, 是在运行时传入的, 变量值被拷贝到函数当中, 且最先defer的最后执行, 所以看到了 4,3,2,1,0

由于defer中的变量在之前运行时都已经设置好了, 有些日志收集类的方法也可以使用它

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
	"log"
	"time"
)

func main() {
	bigSlowOperation("test input")
}

func bigSlowOperation(input interface{}) (output interface{}) {
	defer trace("bigSlowOperation", input)()
	// ...lots of work…
	time.Sleep(2 * time.Second) // simulate slow
	output = "result"
	log.Println("done")
	return
}
func trace(funcName string, args ...interface{}) func() {
	start := time.Now()
	log.Printf("enter %s", funcName)
	return func() {
		log.Printf("func args: %s \n", args)
		log.Printf("exit %s (%s)", funcName, time.Since(start))
	}
}
使用场景一: 延迟释放资源
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

这样把开启和关闭资源的函数写一起, 减少开发时的心智负担

使用场景二: recover

go 里面没有 try catch, 但是有 painc recover, 这个是配合defer才能使用的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import (
    "fmt"
)

func main() {
    defer func() {
        if ok := recover(); ok != nil {
            fmt.Println("recover")
        }
    }()

    panic("error")

}

参考