在循环中延迟释放资源的正确方法?
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
}
我需要在循环中对数据库进行 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
}