39

发出多个defer依赖于顺序的语句,或者推迟一个封装逻辑的匿名函数,是更安全还是更惯用?

例子:

defer os.Remove(tempFile.Name())
defer tempFile.Close()

在上面的例子中,语法是最小的,但延迟的顺序与要执行的逻辑相反。

在下面的例子中,行数更多,“语法”更多,但逻辑顺序更自然:

defer func() {
    tempFile.Close()
    os.Remove(tempFile.Name())
}()

使用哪一个?

4

2 回答 2

36

在此示例中,匿名函数更易于阅读,尤其是在添加错误处理后。

f, err := ioutil.TempFile("", "prefix")
if err != nil {
  log.Println("creating temp file:", err)
  return
}
defer func() {
  err := f.Close()
  if err != nil {
    log.Println("close:", err)
  }
  err = os.Remove(f.Name())
  if err != nil {
    log.Println("remove:", err)
  }
}()

如果你有多个资源,那么多个defers 通常是合适的。

于 2015-09-12T19:10:57.157 回答
10

正如罗斯·莱特的回答所说

如果你有多个资源,那么多个延迟通常是合适的。

2019 年 4 月:但在这种情况下,请考虑 Go 1.13(2019 年第四季度),因为它确实集成了针对Go 问题 14939:“运行时:延迟很慢”Go 问题 6980:“cmd/compile:在堆栈帧中分配一些延迟”的修复"

请参阅Go CL 171758:“cmd/compile,runtime:在堆栈上分配延迟记录”

当一个 defer 在函数体中最多执行一次时,我们可以在堆栈上而不是在堆上为其分配 defer 记录。

这应该会使像这样(非常常见)的延迟更快。

此优化适用于 cmd/go 二进制文件中 370 个静态延迟站点中的 363 个。

name     old time/op  new time/op  delta
Defer-4  52.2ns ± 5%  36.2ns ± 3%  -30.70%  (p=0.000 n=10+10)

2019 年 10 月(几周前发布了 Go 1.13)

CL 190098证实了这一点(Brad Fitzpatrick)

延迟声明的成本 [ go test -run NONE -bench BenchmarkDefer$ runtime]

With normal (stack-allocated) defers only:         35.4  ns/op
With open-coded defers:                             5.6  ns/op
Cost of function call alone (remove defer keyword): 4.4  ns/op

Damien Grisky 补充道

延迟变得更便宜,但恐慌/恢复更昂贵。

Cost of defer: 34ns -> 6ns.
Cost of panic/recover: 62ns -> 255ns

这不是一个糟糕的权衡。


换句话说,虽然使用多个 defer 可能是惯用的,但这种做法受到了性能成本的阻碍,而 Go 1.13+ 不再关注这些成本。
(如Paschalis的博文“什么是延迟?你能跑多少? ”)

如果 defer(在不考虑代码流的情况下应该执行函数调用的地方)可能,这使得实际使用成为可能。

然而, John Refior指出,这defer是同步的

实际上 defer 在函数退出之前立即执行。
它是同步发生的,所以调用者等待 defer 完成。

因此,即使您现在可以有多个 defer,也要确保它们很快,或者,正如 John 所说:

幸运的是,很容易将 goroutine 包装在 a 中defer,为我们提供所需的流量控制和时间,而不会延迟调用者:

func Handler(w http.ResponseWriter, r *http.Request) {
    log.Println("Entered Handler")
    defer func() {
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Exiting goroutine")
        }()
        log.Println("Exiting defer")
    }()
}

defers 通常用于锁定互斥体,或关闭连接或文件描述符,它们所做的工作很快,或者我们希望它在调用者继续之前完成。

但是,当您在进行客户端不需要在 HTTP 处理程序结束时等待的缓慢工作时,使调用异步可以显着改善用户体验。

于 2019-04-27T06:48:50.173 回答