Go `在循环中延迟-什么会更好?

Go `在循环中延迟-什么会更好?,go,Go,我需要对循环中的数据库进行SQL查询: for rows.Next() { fields, err := db.Query(.....) if err != nil { // ... } defer fields.Close() // do something with `fields` } 更好的方法是:保持原样或在循环后移动延迟: for rows.Next() { fields, err := db.Query(.....)

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

for rows.Next() {

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

   // do something with `fields`

}
更好的方法是:保持原样或在循环后移动
延迟

for rows.Next() {

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

   // do something with `fields`
}

defer fields.Close()

或者别的什么?

延迟的整个要点是,它在函数返回之前不会执行,因此在您要关闭的资源打开后立即放置它的合适位置。但是,由于您是在循环内创建资源,因此根本不应该使用defer-否则,在函数退出之前,您不会关闭在循环内创建的任何资源,因此它们将在退出之前堆积起来。相反,您应该在每个循环迭代结束时关闭它们,而无需延迟:

for rows.Next() {

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

   // do something with `fields`

   fields.Close()
}

延迟函数的执行不仅会被延迟,延迟到周围函数返回的那一刻,即使封闭函数突然终止,也会被执行,例如恐慌

“defer”语句调用一个函数,该函数的执行延迟到周围函数返回的那一刻,这可能是因为执行的周围函数已到达其“的结尾,也可能是因为相应的goroutine是”

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

诚然,如果您在循环中分配资源,您不应该简单地使用
defer
,因为这样释放资源不会尽可能早地发生(在每次迭代结束时),只有在
for
语句之后(仅在所有迭代之后)

您应该做的是,如果您有一个分配此类资源的代码段,请将其包装在一个函数(匿名函数或命名函数)中,并且在该函数中您可以使用
延迟
,资源一旦不再需要,就会被释放,重要的是,即使你的代码中有一个可能会引起恐慌的bug

例如:

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

        // do something with `fields`
    }()
}
或者,如果放入命名函数中:

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

    // do something with `fields`
}
并称之为:

for rows.Next() {
    foo(rs)
}
for rows.Next() {
    if err := foo(rs); err != nil {
        // Handle error and return
        return
    }
}
另外,如果希望在第一个错误时终止,可以从
foo()
返回错误:

并称之为:

for rows.Next() {
    foo(rs)
}
for rows.Next() {
    if err := foo(rs); err != nil {
        // Handle error and return
        return
    }
}
还请注意,返回一个错误,当使用
defer
调用该错误时,该错误将被丢弃。如果要检查返回的错误,可以使用如下匿名函数:

func foo(rs *db.Rows) (err error) {
    fields, err := db.Query(...)
    if err != nil {
        return fmt.Errorf("db.Query error: %w", err)
    }
    defer func() {
        if err = fields.Close(); err != nil {
            err = fmt.Errorf("Rows.Close() error: %w", err)
        }
    }()

    // do something with `fields`
    return nil
}

不要在循环内延迟。相关的/可能的重复。此外,在这种情况下,
延迟
甚至不会像OP预期的那样工作,因为它只会关闭循环中最后的
字段
(需要关闭才能正常工作)。顺便说一句,在匿名的
func
中使用
defer
包装循环的内部主体可能是一个好的解决方案。没错-但是即使闭包能够正确工作,它仍然不能很好地工作。这是另一种方式。如果将闭包用于延迟,则只调用最后一个闭包。对于
defer fields.Close()
每个调用都将正确地指向不同的指针,当然,这仍然是错误的,因为一旦func完成,所有调用都将被调用。这是否意味着如果在循环的每个迭代中分配多个资源,并且发生错误,并且在if子句中死机,而没有首先关闭每个打开的资源,上次迭代期间分配的资源将无法正确关闭?也就是说,在for循环中,不能依靠自动资源清理,必须手动清理在该循环迭代中分配的所有资源,以防出现错误?如果您不延迟关闭,并且您在恢复过程中没有关闭资源就恢复了恐慌,则是的,您可能会泄漏资源,而不管其他情况如何。恐慌应该是罕见的,通常应该是致命的。如果你要恢复恐慌,你应该敏锐地意识到后果。这个答案显然比公认的答案要好,因为它考虑了恐慌,并提供了一个很好的示例解决方案。我同意它更好。我还想通过不忽略Close()函数的err值来改进它。@aclowkay感谢您的注释,更新了答案(还有其他改进)。