说说Go中defer的坑

defer nil 函数

如果一个延迟函数被赋值为 nil  运行时的 panic 异常会发生在外围函数执行结束后而不是 defer 的函数被调用的时候。

package main

import (
    "fmt"
)

func main() {
  var run func() = nil
  defer run()
  fmt.Println("runs")
}
执行结果:
runs
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4503e8]

goroutine 1 [running]:
main.main()
	/root/workspace/defer10.go:11 +0x85
exit status 2

不要在循环中使用 defer,除非你清楚自己在做什么,因为它们的执行结果常常会出人意料。

defer row.Close() 在循环中的延迟函数会在函数结束过后运行,而不是每次 for 循环结束之后。这些延迟函数会不停地堆积到延迟调用栈中,最终可能会导致一些不可预知的问题。

解决方法1:不要使用defer

解决方法2:将任务转交给另一个函数然后在里面使用 defer,在下面这种情况下,延迟函数会在每次匿名函数执行结束后执行。

在代码块中使用defer

期望在代码块执行结束后调用延迟函数,但实际上,往往达不到期望的效果

因为:延迟函数的对象是函数而非代码块

package main

import (
    "fmt"
)
func main() {
  {
    defer func() {
      fmt.Println("block: defer runs")
    }()
    fmt.Println("block: ends")
  }
  fmt.Println("main: ends")
}
执行结果:
block: ends
main: ends
block: defer runs

解决方法:将代码块放到匿名函数里

package main

import (
    "fmt"
)

func main() {
  func() {
    defer func() {
      fmt.Println("func: defer runs")
    }()
    fmt.Println("func: ends")
  }()
  fmt.Println("main: ends")
}
执行结果:
func: ends
func: defer runs
main: ends

需要说明的一点是:对于for、switch这样的块可以达到延迟的目的,例如我们上文讲到的。

尽量不要在延迟调用含有闭包的函数

package main

import (
    "log"
    "time"
)

func main() {
    bigSlowOperation()
}

func bigSlowOperation() {
    defer trace("bigSlowOperation")
    time.Sleep(10 * time.Second)
    log.Printf("wait 10 Second")
}

func trace(msg string) func() {
    start := time.Now()
    log.Printf("enter %s", msg)
    return func() {
        log.Printf("exit %s (%s)", msg, time.Since(start))
    }
}
执行结果:
2018/07/26 00:02:07 wait 10 Second
2018/07/26 00:02:07 enter bigSlowOperation

执行结果log.Printf("exit %s (%s)", msg, time.Since(start))没被执行

改进:修改defer trace("bigSlowOperation")为defer trace("bigSlowOperation")()

package main

import (
    "log"
    "time"
)

func main() {
    bigSlowOperation()
}

func bigSlowOperation() {
    defer trace("bigSlowOperation")()
    time.Sleep(10 * time.Second)
    log.Printf("wait 10 Second")
}

func trace(msg string) func() {
    start := time.Now()
    log.Printf("enter %s", msg)
    return func() {
        log.Printf("exit %s (%s)", msg, time.Since(start))
    }
}
执行结果:
2018/07/25 23:59:20 enter bigSlowOperation
2018/07/25 23:59:30 wait 10 Second
2018/07/25 23:59:30 exit bigSlowOperation (10.001322235s)

等价于

package main

import (
    "log"
    "time"
)

func main() {
    bigSlowOperation()
}

func bigSlowOperation() {
    trace1 := trace("bigSlowOperation")
    defer trace1()
    time.Sleep(10 * time.Second)
    log.Printf("wait 10 Second")
}

func trace(msg string) func() {
    start := time.Now()
    log.Printf("enter %s", msg)
    return func() {
        log.Printf("exit %s (%s)", msg, time.Since(start))
    }
}
执行结果:
2018/07/25 23:59:20 enter bigSlowOperation
2018/07/25 23:59:30 wait 10 Second
2018/07/25 23:59:30 exit bigSlowOperation (10.001322235s)

延迟方法(注意值传递和指针传递的区别)

package main

import (
    "fmt"
)
type Student struct {
  name string
}
func (c Student) PrintName() {
  fmt.Println(c.name)
}

func main() {
  c := Student{name: “xiaoming”}
  defer c.PrintName()
  c.name = “xiaoxiao”
}
执行结果:
xiaoming
package main

import (
    "fmt"
)
type Student struct {
  name string
}
func (c *Student) PrintName() {
  fmt.Println(c.name)
}

func main() {
  c := Student{name: “xiaoming”}
  defer c.PrintName()
  c.name = “xiaoxiao”
}
执行结果:
xiaoxiao

我们知道,当外围函数还没有返回的时候,Go 的运行时就会立刻将传递给延迟函数的参数保存起来。

因此,当一个以值作为接收者的方法被 defer 修饰时,接收者会在声明时被拷贝,此时任何对拷贝的修改都将不可见,因为,接收者也同时是输入的参数,当使用 defer 修饰时会立刻得出参数的值(也就是 "xiaoming" )

在另一种情况下,当被延迟调用时,接收者为指针对象,此时虽然会产生新的指针变量,但其指向的地址依然与上例中的 "c" 指针的地址相同。因此,任何修改都会完美地作用在同一个对象中

特别说明:部分例子借助互联网,在此表示感谢。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值