[toc]
注意 defer 的调用时机
有时候我们会像下面一样使用 defer 去关闭一些资源:
1 2 3 4 5 6 7 8 9 10 11 12 13
| func readFiles(ch <-chan string) error { for path := range ch { file, err := os.Open(path) if err != nil { return err } defer file.Close() } return nil }
|
因为defer会在方法结束的时候调用,但是如果上面的 readFiles 函数永远没有 return,那么 defer 将永远不会被调用,从而造成内存泄露。并且 defer 写在 for 循环里面,编译器也无法做优化,会影响代码执行性能。
为了避免这种情况,我们可以 wrap 一层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| func readFiles(ch <-chan string) error { for path := range ch { if err := readFile(path); err != nil { return err } } return nil }
func readFile(path string) error { file, err := os.Open(path) if err != nil { return err }
defer file.Close()
return nil }
|
注意 defer 的参数
defer 声明时会先计算确定参数的值。
1 2 3 4 5 6 7 8 9 10
| func a() { i := 0 defer notice(i) i++ return }
func notice(i int) { fmt.Println(i) }
|
在这个例子中,变量 i 在 defer 被调用的时候就已经确定了,而不是在 defer执行的时候,所以上面的语句输出的是 0。
所以我们想要获取这个变量的真实值,应该用引用:
1 2 3 4 5 6
| func a() { i := 0 defer notice(&i) i++ return }
|
defer 下的闭包
1 2 3 4 5 6 7 8 9 10 11 12
| func a() int { i := 0 defer func() { fmt.Println(i + 1) }() i++ return i+10 }
func TestA(t *testing.T) { fmt.Println(a()) }
|
如果换成闭包的话,实际上闭包中对变量i是通过指针传递的,所以可以读到真实的值。但是上面的例子中 a 函数返回的是 11 是因为执行顺序是:
1
| 先计算(i+10)-> (call defer) -> (return)
|