Go语言 多个defers与延迟匿名函数

ztmd8pv5  于 2023-08-01  发布在  Go
关注(0)|答案(2)|浏览(105)

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

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

字符串
在上面的情况下,语法是最小的,但是defers的顺序与要执行的逻辑相反。
在下面的例子中,有更多的行,更多的“语法”,但逻辑是在一个更自然的顺序:

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


用哪一个?

tyu7yeag

tyu7yeag1#

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

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通常是合适的。

nlejzf6q

nlejzf6q2#

Ross Light回答states
如果您有多个资源,那么多个延迟通常是合适的。
2019年4月:但在这种情况下,考虑Go 1.13(2019年第4季度),因为它确实集成了go issue 14939: "runtime: defer is slow"go issue 6980: "cmd/compile: allocate some defers in stack frames"的修复
参见Go CL 171758: "cmd/compile,runtime: allocate defer records on the stack"
当一个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几周前发布)
这个is confirmed (Brad Fitzpatrick)CL 190098
defer语句的成本[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
延迟变得更便宜,但恐慌/恢复更昂贵。

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


这是一个不错的权衡。
换句话说,虽然使用多个defer可能是惯用的,但这种做法受到性能成本的阻碍,而Go 1.13+不再关注性能成本。
(as由Paschalis的博客文章“What is a defer? And how many can you run?”说明)
如果defer(在不考虑代码流的情况下应该执行函数调用的地方)可能,这就有了实际的用途。
John Refior注意到,defer is synchronous
实际上,defer是在函数退出之前立即执行的。
并且它是同步发生的,因此调用方等待defer完成。
因此,即使你现在可以有多个defer,也要确保它们是快速的,或者,正如John所指出的:
幸运的是,可以很容易地将goroutine Package 在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处理程序结束时等待的缓慢工作时,使调用异步可以大大改善用户体验。

相关问题