在Go中,采用了一种务实的方法来管理资源。与其他语言中的try-finally不同,Go中有一个defer:内置机制,可以记录“延迟”函数,并在离开作用域时执行。这个工具通常用于自动释放资源(文件、网络连接)。
如果忘记调用文件或连接的Close,可能会导致资源泄漏或阻塞——在服务器和文件应用程序中,这一点至关重要。使用defer的延迟调用确保即使在发生错误或panic时,也会调用结束函数。然而,也有特殊情况,当不正确使用defer时会导致错误:在循环中调用defer、传递不正确的对象或处理大量defer可能会导致开销。
始终在成功打开资源后立即调用defer f.Close(),以避免忘记关闭。不要在紧密循环中使用defer以提高速度和节省内存,尤其是在打开大量文件时。尽量将文件/资源的打开封装在一个函数中,以最小化作用域。
示例代码:
file, err := os.Open("data.txt") if err != nil { log.Fatal(err) } defer file.Close() // 确保关闭 // ... 与文件的工作
关键特点:
defer何时执行,其参数为何时计算?
defer中的函数参数在声明defer时立即计算,而不是在执行defer时。
示例代码:
func main() { a := 1 defer fmt.Println(a) // 记住1 a = 42 } // 输出1
defer会导致内存泄漏或性能下降吗?
是的,如果在循环中使用defer,打开大量文件或对象时,每个defer都被写入延迟调用栈,该栈仅在函数退出时清空,从而导致内存不必要增长。
示例代码:
for i := 0; i < 10000; i++ { f, _ := os.Open("file.txt") defer f.Close() // 将所有10000个文件打开直至main结束 }
如果调用f.Close()返回错误,而错误被“吞掉”了,会发生什么?
标准做法是记录资源关闭的错误。如果忽略这一点,可能会错过故障或文件部分保存的问题,例如在临时文件未删除时或网络故障时。
在处理文件的循环中,开发者为每个文件都放了defer f.Close()。这导致同时打开了成千上万个文件,程序执行变慢,系统中的文件描述符耗尽。
优点:
缺点:
在循环中,每个文件的处理在一个单独的函数中进行,其中defer f.Close()仅在处理时调用一次,并立即释放资源。
优点:
缺点: