defer延迟函数

Go语言的defer算是一个语言的新特性,至少对比当今主流编程语言如此.

  1. A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking. defer语句调用一个函数,这个函数执行会推迟,直到外围的函数返回,或者外围函数运行到最后,或者相应的goroutine panic

defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。

  1. f,err := os.Open(filename)
  2. if err != nil {
  3. panic(err)
  4. }
  5. defer f.Close()

如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。

在处理其他资源时,也可以采用defer机制,比如对文件的操作:

  1. package ioutil
  2. func ReadFile(filename string) ([]byte, error) {
  3. f, err := os.Open(filename)
  4. if err != nil {
  5. return nil, err
  6. }
  7. defer f.Close()
  8. return ReadAll(f)
  9. }

也可以处理互斥锁:

  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()
  6. return m[key]
  7. }

调试复杂程序时,defer机制也常被用于记录何时进入和退出函数。

此外在使用defer函数的时候也会遇到些意外的情况,那就是defer使用时需要注意的坑: 先来看看几个例子。例1:

  1. func f() (result int) {
  2. defer func() {
  3. result++
  4. }()
  5. return 0
  6. }

例2:

  1. func f() (r int) {
  2. t := 5
  3. defer func() {
  4. t = t + 5
  5. }()
  6. return t
  7. }

例3:

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

自己可以先跑下看看这三个例子是不是和自己想的不一样的呢!结果确实是的例1的正确答案不是0,例2的正确答案不是10,如果例3的正确答案不是6……

defer是在return之前执行的。这个在 官方文档中是明确说明了的。要使用defer时不踩坑,最重要的一点就是要明白,return A这一条语句并不是一条原子指令!

  1. 返回值 = A
  2. 调用defer函数
  3. 空的return

接着我们看下例1,它可以改写成这样:

  1. func f() (result int) {
  2. result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令
  3. func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
  4. result++
  5. }()
  6. return
  7. }

所以例子1的这个返回值是1。

再看例2,它可以改写成这样:

  1. func f() (r int) {
  2. t := 5
  3. r = t //赋值指令
  4. func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
  5. t = t + 5
  6. }
  7. return //空的return指令
  8. }

所以这个的结果是5。

最后看例3,它改写后变成:

  1. func f() (r int) {
  2. r = 1 //给返回值赋值
  3. func(r int) { //这里改的r是传值传进去的r,不会改变要返回的那个r值
  4. r = r + 5
  5. }(r)
  6. return //空的return
  7. }

所以这个例子3的结果是1

defer确实是在return之前调用的。但表现形式上却可能不像。本质原因是return A语句并不是一条原子指令,defer被插入到了赋值 与ret之间,因此可能有机会改变最终的返回值。