loops - 在循环中使用 defer 释放资源的正确方法?

标签 loops go deferred-execution

我需要在循环中对数据库进行 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()

还是别的什么?

最佳答案

延迟函数的执行不仅延迟,延迟到周围函数返回的那一刻,即使封闭函数突然终止,它也会执行,例如 panic 。 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 语句来确保它被释放,即使您的其他代码 panic 到防止泄漏内存或其他系统资源。

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

你应该做的是,如果你有一个分配这些资源的片段,将它包装在一个函数中——匿名函数或命名函数——并且在该函数中你可以使用 defer,并且资源将在不再需要时立即释放,重要的是即使您的代码中存在可能导致 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)
}

此外,如果您想在第一个错误时终止,您可以从 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()返回一个错误,当使用 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
}

关于loops - 在循环中使用 defer 释放资源的正确方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45617758/

相关文章:

php - 如何创建向后循环以查找第一个匹配文件

loops - 电源查询循环

bash - 对多个目录中的视频文件进行递归编码

java - 对于 i0 变量

go - 如果修改信号处理程序中的ctx.rip和ctx.rsp会发生什么

c# - linq 查询中的垃圾回收

go - 初始化嵌套结构图

json - 如何在Golang上的RESTful api中返回没有转义字符串的Json数据

c# - IEnumerable 的多重枚举 - StackOverflowException

go - 请解释一下DEFER在golang中的行为