在Go语言中,以下内容Open带有延迟名称Close是惯用的:
func example() {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
}
Run Code Online (Sandbox Code Playgroud)
如果没有我该怎么办defer f.Close()?
当我调用此函数并f超出范围时,它会自动关闭文件还是具有僵尸文件句柄?
如果它自动关闭,那么什么时候才执行呢?
确实,垃圾回收时文件是关闭的,但是...如亚历山大·莫罗佐夫(Alexander Morozov)的 “ Go终结器的奥秘 ” -LK4D4math所述:
在Go中,我们既有GC用户
,也有Pro-users用户:)因此,我认为显式调用Close总是比magic finalizer好。
亚历山大补充说:
终结器的问题在于您无法控制它们,更重要的是,您不期望它们。
看下面的代码:
func getFd(path string) (int, error) {
f, err := os.Open(path)
if err != nil {
return -1, err
}
return f.Fd(), nil
}
Run Code Online (Sandbox Code Playgroud)
在为Linux编写某些内容时,从路径获取文件描述符是很常见的操作。
但是该代码是不可靠的,因为当您从中返回时getFd(),f它会丢失其最后一个引用,因此您的文件注定早晚要关闭(下一个GC周期到来时)。在这里,问题不在于文件将被关闭,而是没有文档记录和根本没有期望。
有人提议扩展终结器并检测泄漏(例如文件描述符泄漏)
但是... Russ Cox令人信服地将其消除:
对这个主题感兴趣的任何人都应该阅读Hans Boehm的论文“ Destructors,Finalizers和Synchronization ”。
它极大地影响了我们决定限制Go中终结器范围的决定。
它们是允许与关联的堆内存同时回收非(堆内存)资源的必不可少的手段,但与大多数人最初认为的一样,它们的能力固有地要受到限制。无论是在实现中,在标准库中还是在x存储库中,我们都不会扩展终结器的范围,也不会鼓励其他人扩展该范围。
如果您要跟踪手动管理的对象,最好使用
runtime/pprof.NewProfile。例如,在Google的源代码树中,我们有一个Google范围的“文件”抽象,为此的Go包装程序包声明了一个全局变量:
var profiles = pprof.NewProfile("file")
Run Code Online (Sandbox Code Playgroud)
创建新文件的函数结尾说:
profiles.Add(f, 2)
return f
Run Code Online (Sandbox Code Playgroud)
然后
f.Close做
profiles.Remove(f)
Run Code Online (Sandbox Code Playgroud)
然后,我们可以从
/debug/pprof/file或从中获取所有正在使用的文件的配置文件,无论是“泄漏”还是其他pprof.Lookup("file").WriteTo(w, 0)。
该配置文件包括堆栈跟踪。