多重延迟与延迟匿名函数

Multiple defers vs deferred anonymous function

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

示例:

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

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

在下面的例子中,行数更多,"syntax",但逻辑顺序更自然:

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

使用哪一个?

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

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)
  }
}()

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

Ross Light answer :

If you have multiple resources, then multiple defers is generally appropriate.

2019 年 4 月:但在那种情况下,请考虑 Go 1.13(2019 年第四季度),因为它确实集成了 go issue 14939: "runtime: defer is slow" and go issue 6980: "cmd/compile: allocate some defers in stack frames"

的修复程序

Go CL 171758: "cmd/compile,runtime: allocate defer records on the stack"

When a defer is executed at most once in a function body, we can allocate the defer record for it on the stack instead of on the heap.

This should make defers like this (which are very common) faster.

This optimization applies to 363 out of the 370 static defer sites in the cmd/go binary.

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 年(Go 1.13 于几周前发布)

这个is confirmed (Brad Fitzpatrick) with CL 190098:

Cost of defer statement [ 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 adds:

Defer gets cheaper, but panic/recover is more expensive.

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

这是一个不错的权衡。


换句话说,虽然使用多个 defer 可能是惯用的,但这种做法因性能成本而受阻,而 Go 1.13+ 不再是一个问题。
(如Paschalis's blog post "What is a defer? And how many can you run?所示)

这使得 practical use if defer(无论代码流如何都应执行函数调用的地方)成为可能。

John Refior notes, however, that defer is synchronous:

Actually defer is executed immediately before the function exits.
And it occurs synchronously, so the caller waits for defer to complete.

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

Fortunately it’s easy to wrap a goroutine in a defer, giving us the flow control and timing we want, without delaying the caller:

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")
    }()
}

Often defers are used for locking a mutex, or closing a connection or file descriptor, and the work they do is fast, or we want it to complete before the caller moves on.

But when you’re doing slow work that the client shouldn’t need to wait for at the end of an HTTP handler, making the call asynchronous can substantially improve user experience.