编程后端开发人员

defer在Go的循环和lambda函数中是如何工作的?在循环中使用defer可能会带来什么危险?

用 Hintsage AI 助手通过面试

回答

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,这导致生产服务器上的打开描述符限制溢出,服务停止运行。