defer会在周围函数退出时延迟执行,即使是因为panic或return导致的退出。当在循环中使用defer时,所有的延迟调用会累积在defer调用栈中,并在周围函数结束时按逆序执行。
这可能导致意外的资源耗尽和延迟,因为所有延迟的函数将在退出函数体后同时调用,而不是在每次循环迭代后调用。
代码示例:
func readFiles(files []string) { for _, name := range files { f, _ := os.Open(name) defer f.Close() // 资源只在整个函数完成后释放 // 处理文件 ... } }
在这个示例中,文件会保持打开状态直到函数的结束,这可能会导致在文件数量较多时出现描述符泄漏。
如果在循环内使用defer关闭资源,会发生什么?为什么这并不总是最佳选择?
回答: 所有defer调用都会积累,并且只有在函数结束后触发,而不是在每次迭代后。这会导致资源(比如打开的文件)被释放得太晚。
正确的做法:
for _, name := range files { f, _ := os.Open(name) // defer f.Close() // 不可以! // 正确: process(f) f.Close() }
故事
在日志加载项目中出现了一个问题:服务突然无法打开新的文件,尽管文件不多。原因是循环中的defer。所有文件都被打开,但关闭被延迟到函数的末尾。将其重写为在处理后显式调用Close()后,问题消失了。
故事
在为大列表收集指标的服务中,使用defer在数据遍历循环中关闭数据库连接。随着迭代次数的增加,出现了延迟,并超过了打开连接的上限,服务开始因错误“too many open connections”而崩溃。
故事
一位工程师希望实现“优雅”的清理,在读取大量文件的循环中应用了defer,这导致生产服务器上的打开描述符限制溢出,服务停止运行。