在Go中,为了确保清理或结束对资源的使用,我们将延迟调用(defer)与匿名闭包(closures)结合使用。这种模式能够将清理逻辑组合在一起,并合理处理错误,从而提供可读和可靠的代码。
问题背景:
Defer来源于其他语言,大大简化了Go开发者的生活。defer与闭包的结合成为了确保文件、连接和任何外部资源清理的标准,尤其是在可能有多个退出点(return, panic)的块中。
问题:
在复杂的资源清理逻辑(如文件、连接、位置)中,需要确保即使在发生错误或函数退出的情况下,也能进行清理。如果使用不当,可能会导致泄漏、清理顺序不正确或无意义的错误。
解决方案:
使用defer与匿名函数(closure),以:
示例代码:
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // 与文件的工作逻辑 fmt.Fprintln(f, "Hello world") return // defer将在这里return时也会执行 } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("Error:", err) } }
关键特性:
使用在延迟闭包中引用的变量时,变量是在defer声明时固定,还是在实际调用时固定?
它们在defer声明时固定,但如果闭包通过引用访问这些变量,将在defer执行时使用它们的值。有时这会导致意想不到的结果。
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // 打印 2, 2, 2 }
可以通过参数将值传递给闭包,以避免引用捕获吗?
是的,可以为匿名函数声明参数并传递当前值——这样值就会作为副本“冻结”。
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // 打印 2, 1, 0 }
如果在延迟的闭包中发现了恐慌,怎么办?如何处理它?
在闭包内部使用recover()结构,以防止恐慌泄漏,并实现平滑恢复。
defer func() { if r := recover(); r != nil { log.Println("Recovered:", r) } }()
代码打开多个文件,却忘记放置defer f.Close()。在出现错误时返回控制,部分文件未被清理,导致资源泄漏。
优点:
缺点:
对所有的数据库操作使用延迟闭包:即使文件没有完全写入,也能妥善关闭文件流并处理错误。
优点:
缺点: