循环中的"延迟" - 什么会更好?

dem*_*mas 20 go

我需要在循环中对数据库进行SQL查询:

for rows.Next() {

   fields, err := db.Query(.....)
   if err != nil {
      // ...
   }
   defer fields.Close()

   // do something with `fields`

}
Run Code Online (Sandbox Code Playgroud)

什么会更好:保持原样或defer循环后移动:

for rows.Next() {

   fields, err := db.Query(.....)
   if err != nil {
      // ...
   }

   // do something with `fields`
}

defer fields.Close()
Run Code Online (Sandbox Code Playgroud)

或者是其他东西 ?

icz*_*cza 39

延迟函数的执行不仅被延迟,延迟到周围函数返回的时刻,即使封闭函数突然终止(例如恐慌)也会执行.规格:推迟陈述:

"defer"语句调用一个函数,该函数的执行被推迟到周围函数返回的那一刻,因为周围的函数执行了一个return语句,到达了它的函数体的末尾,或者因为相应的goroutine是恐慌.

每当您创建一个值或资源提供正确关闭/处置它的方法时,您应该始终使用defer语句来确保它被释放,即使您的其他代码发生混乱以防止泄漏内存或其他系统资源.

确实,如果你在一个循环中分配资源,你不应该简单地使用defer,因为释放资源不会尽可能早地发生(在每次迭代结束时),只在for声明之后(仅在所有迭代之后) ).

你应该做的是,如果你有一个分配这些资源的代码片段,将它包装在一个函数中 - 无论是匿名函数还是命名函数 - 并且在你可能使用的函数中defer,资源将在它们为no时立即释放需要更长时间,重要的是即使您的代码中存在可能出现恐慌的错误.

例:

for rows.Next() {
    func() {
        fields, err := db.Query(...)
        if err != nil {
            // Handle error and return
            return
        }
        defer fields.Close()

        // do something with `fields`
    }()
}
Run Code Online (Sandbox Code Playgroud)

或者如果放入命名函数:

func foo(rs *db.Rows) {
    fields, err := db.Query(...)
    if err != nil {
        // Handle error and return
        return
    }
    defer fields.Close()

    // do something with `fields`
}
Run Code Online (Sandbox Code Playgroud)

并称之为:

for rows.Next() {
    foo(rs)
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案明显优于公认的答案,因为它考虑了恐慌,并提供了一个很好的样本解决方案. (9认同)

Adr*_*ian 35

整个问题defer是它在函数返回之前不会执行,所以在打开要关闭的资源之后立即放置它的适当位置.但是,由于你在循环中创建了资源,所以你根本不应该使用defer - 否则,你不会关闭循环中创建的任何资源,直到函数退出,所以它们会堆积到然后.相反,您应该在每次循环迭代结束时关闭它们,而不是 defer:

for rows.Next() {

   fields, err := db.Query(.....)
   if err != nil {
      // ...
   }

   // do something with `fields`

   fields.Close()
}
Run Code Online (Sandbox Code Playgroud)

  • 这是另一条路。如果您使用闭包延迟,则只会调用最后一个。对于`defer fields.Close()`,每次调用都会正确指向不同的指针,当然,这仍然是错误的,因为一旦func完成,所有调用都会被调用。 (3认同)
  • 除此之外,在这种情况下,`defer`甚至无法如OP期望的那样工作,因为它只会关闭循环中的最后一个`fields'(需要关闭才能正常工作)。顺便说一句,用“ defer”将匿名函数内部的循环内部包裹起来可能是个不错的选择。 (2认同)

Bia*_*uan 5

可以构造一个局部函数来解决这个问题

    for i := 0; i < 5; i++ {
        func() {
            f, err := os.Open("/path/to/file")
            if err != nil {
                log.Fatal(err)
            } else {
                defer f.Close()
            }
        }()
    }
Run Code Online (Sandbox Code Playgroud)