多个延迟 vs 延迟的匿名函数

huangapple go评论107阅读模式
英文:

Multiple defers vs deferred anonymous function

问题

在这种情况下,更安全和更符合惯用法的做法是使用依赖于顺序的多个defer语句,还是使用封装逻辑的匿名函数进行延迟处理?

示例:

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

在上述情况下,语法最简洁,但是defer语句的顺序与要执行的逻辑相反。

在下面的情况下,代码行更多,语法更复杂,但是逻辑顺序更自然:

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

应该使用哪种方式?

英文:

Is it safer or more idiomatic to issue multiple defer statements which are dependent on the order, or to defer an anonymous function which packages the logic?

Examples:

<!-- language: lang-go -->

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

In the case above the syntax is minimal, yet the order of defers is reverse to the logic to be executed.

In the case below there are more lines, more "syntax", but the logic is in a more natural order:

<!-- language: lang-go -->

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

Which one to use?

答案1

得分: 41

在这个例子中,匿名函数更容易阅读,特别是一旦你添加了错误处理。

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`通常是适当的

<details>
<summary>英文:</summary>

In this example, the anonymous function is easier to read, especially once you add in error handling.

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

If you have multiple resources, then multiple `defer`s is generally appropriate.

</details>



# 答案2
**得分**: 17

根据[Ross Light][1]的回答

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

2019年4月但在这种情况下考虑使用Go 1.132019年第四季度),因为它集成了对[go issue 14939: "runtime: defer is slow"][3][go issue 6980: "cmd/compile: allocate some defers in stack frames"][4]的修复

参见[Go CL 171758: "cmd/compile,runtime: allocate defer records on the stack"][5]

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

这应该使得像这样的defers非常常见更快

这个优化适用于cmd/go二进制文件中的370个静态defer站点中的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刚刚发布几周

这个[得到了确认Brad Fitzpatrick][6]使用了[CL 190098][7]

defer语句的成本 [`go test -run NONE -bench BenchmarkDefer$ runtime`]

使用普通堆栈分配的defer35.4 ns/op
使用开放式编码的defer5.6 ns/op
仅函数调用的成本去掉defer关键字):4.4 ns/op

[Damien Grisky补充说][8]

defer变得更便宜了但panic/recover变得更昂贵了

defer的成本34ns -> 6ns
panic/recover的成本62ns -> 255ns

这不是一个坏的权衡

----

换句话说虽然使用多个defer可能是惯用的但这种做法受到了性能成本的限制而在Go 1.13+中不再是问题
正如[Paschalis][9]的博文[What is a defer? And how many can you run?][10]所示

这使得在应该执行函数调用的地方无论代码流如何可以实现实际使用defer

然而[John Refior][12]指出`defer`是同步的

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

因此即使现在可以有多个defer请确保它们执行速度快或者如John所指出的那样

幸运的是我们可以在`defer`中包装一个goroutine这样就可以获得我们想要的流程控制和时间控制而不会延迟调用者

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

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

但是,当你在HTTP处理程序的末尾执行不需要客户端等待的慢速工作时,将调用异步化可以大大提高用户体验。

英文:

As Ross Light answer states:

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

April 2019: But in that case, consider Go 1.13 (Q4 2019), as it does integrate a fix for go issue 14939: "runtime: defer is slow" and go issue 6980: "cmd/compile: allocate some defers in stack frames"

See 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)


Oct. 2019 (Go 1.13 is released a few weeks ago)

This 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

But Damien Grisky adds:

> Defer gets cheaper, but panic/recover is more expensive.
>
> Cost of defer: 34ns -> 6ns.
> Cost of panic/recover: 62ns -> 255ns

That is not a bad trade-off.


In other words, while using multiple defer can be idiomatic, that practice was held back by performance costs which are no longer a concern with Go 1.13+.
(as illustrated by Paschalis's blog post "What is a defer? And how many can you run?")

That makes practical use if defer (in places where a function call should be executed irrespective of the code flow) possible.

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.

So even if you can now have multiple defer, make sure they are fast, or, as John notes:

> Fortunately it’s easy to wrap a goroutine in a defer, giving us the flow control and timing we want, without delaying the caller:
>
> go
&gt; func Handler(w http.ResponseWriter, r *http.Request) {
&gt; log.Println(&quot;Entered Handler&quot;)
&gt; defer func() {
&gt; go func() {
&gt; time.Sleep(5 * time.Second)
&gt; log.Println(&quot;Exiting goroutine&quot;)
&gt; }()
&gt; log.Println(&quot;Exiting defer&quot;)
&gt; }()
&gt; }
&gt;

>
> 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.

huangapple
  • 本文由 发表于 2015年9月13日 01:47:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/32541870.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定