在循环中延迟释放资源的正确方法?

Proper way to release resources with defer in a loop?

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

for rows.Next() {

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

   // do something with `fields`

}

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

for rows.Next() {

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

   // do something with `fields`
}

defer fields.Close()

或者别的什么?

defer 的全部要点是它直到函数 returns 才执行,因此放置它的适当位置将是在您要关闭的资源打开之后立即执行。但是,由于您是在循环内创建资源,因此根本不应该使用 defer - 否则,在函数退出之前,您不会关闭在循环内创建的任何资源,因此它们会堆积起来直到然后。相反,您应该在每次循环迭代结束时关闭它们,without defer:

for rows.Next() {

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

   // do something with `fields`

   fields.Close()
}

延迟函数的执行不仅被延迟,延迟到周围函数 returns 的时刻,即使封闭函数突然终止,它也会执行,例如恐慌。 Spec: Defer statements:

A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

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

的确,如果您在循环中分配资源,您不应该简单地使用 defer,因为这样就不会尽早释放资源和 should (在每次迭代结束时),仅在 for 语句之后(仅在所有迭代之后)。

你应该做的是,如果你有一个分配此类资源的片段,将其包装在一个函数中——匿名函数或命名函数——,在该函数中你可以使用 defer,并且资源一旦不再需要就会被释放,重要的是即使你的代码中有一个 bug 可能会 panic。

示例:

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

此外,如果您想在第一个错误时终止,您可以 return 来自 foo() 的错误:

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

    // do something with `fields`
    return nil
}

并调用它:

for rows.Next() {
    if err := foo(rs); err != nil {
        // Handle error and return
        return
    }
}

另请注意,Rows.Close() return 是一个错误,当使用 defer 调用时,该错误会被丢弃。如果我们想检查 returned 错误,我们可以使用这样的匿名函数:

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
}