kos*_*tix 37
在Go生态系统中,存在一种无处不在的习惯用法,用于处理包裹珍贵(和/或外部)资源的对象:指定用于释放该资源的特殊方法,通常通过该机制显式调用defer.
通常会命名此特殊方法Close(),并且对象的用户在完成对象表示的资源时必须显式调用它.该io标准包中甚至有专门的接口,io.Closer,宣称单一的方法.在各种资源(如TCP套接字,UDP端点和文件)上实现I/O的对象都满足io.Closer,并且预计Close在使用后将明确显示.
调用这样的清理方法通常是通过defer保证方法将运行的机制来完成的,无论是否在资源获取之后执行某些代码panic().
你可能还注意到没有隐含的"析构函数"在Go中没有隐含的"构造函数".这实际上与Go中没有"类"无关:语言设计者尽可能地避免使用魔法.
请注意,Go对此问题的处理方法可能看起来有点低技术,但实际上它是运行时具有垃圾收集功能的唯一可行解决方案.在具有对象但没有GC的语言中,例如C++,破坏对象是一个定义明确的操作,因为对象在超出范围或delete在其内存块上被调用时会被销毁.在使用GC的运行时,GC扫描将来会在某些大多数不确定的位置销毁该对象,并且可能根本不会销毁该对象. 因此,如果对象包含一些宝贵的资源,那么该资源可能根本无法回收,正如@twotwotwo在各自的答案中所解释的那样.
如果您熟悉.NET,它会以类似于Go的方式处理资源清理:包装一些宝贵资源的对象必须实现IDisposable接口,并且该接口Dispose()导出的方法必须是当你完成这样一个对象时显式调用.C#通过using语句为这个用例提供了一些语法糖,这使得编译器Dispose()在超出上述语句声明的作用域时安排调用该对象.在Go中,您通常会defer调用清理方法.
还有一点需要注意.Go希望你非常认真地对待错误(与大多数主流编程语言不同,他们的"只是抛出一个异常并且不会对其他地方发生的事情以及程序处于什么状态"的态度给出一个fsck)所以你可能会考虑检查至少一些清理方法调用的错误返回.
一个很好的例子是os.File表示文件系统上文件的类型的实例.有趣的是,由于合法的原因,调用Close()打开的文件可能会失败,如果您正在写入该文件,这可能表明您写入该文件的所有数据并未实际落入文件系统中. 有关说明,请阅读close(2)手册中的"注释"部分.
换句话说,只是做类似的事情
fd, err := os.Open("foo.txt")
defer fd.Close()
Run Code Online (Sandbox Code Playgroud)
对于99.9%的情况下的只读文件是可以的,但对于开放写入的文件,您可能希望实现更多涉及的错误检查和一些处理它们的策略(仅仅是报告,等待然后重试,然后请求-maybe-retry或其他).
two*_*two 15
runtime.SetFinalizer(ptr, finalizerFunc)设置终结器 - 不是析构函数,而是另一种可能最终释放资源的机制.阅读那里的文档了解详细信息,包括缺点.它们可能在对象实际无法访问之后才运行,如果程序首先退出,它们可能根本不运行.他们还推迟释放内存以进行另一个GC循环.
如果您正在获取一些尚未拥有终结器的有限资源,并且如果程序一直泄漏,程序最终将无法继续,您应该考虑设置终结器.它可以减少泄漏.stdlib中的终结器已经清除了无法访问的文件和网络连接,因此只有其他类型的资源才能使用自定义文件和网络连接.最明显的类是你通过syscall或获得的系统资源cgo,但我可以想象其他人.
虽然他们可以修补一些错误,但你不应该依赖终结者.它们不会运行直到GC运行.因为程序可以在下一个GC之前退出,所以你不能依赖它们来做必须完成的事情,比如将缓冲的输出刷新到文件系统.假设GC 确实发生了,它可能不会很快发生:如果终结器负责关闭网络连接,可能是远程主机在GC之前达到了对您的打开连接的限制,或者您的进程达到其文件描述符限制,或者您运行从短暂的港口,或其他东西.因此,当需要使用终结器并希望尽快完成时,更好地defer进行清理.
SetFinalizer在日常的Go编程中你没有看到很多调用,部分原因是最重要的调用是在标准库中,主要是因为它们的适用范围有限.
简而言之,终结器可以通过在长期运行的程序中释放被遗忘的资源来提供帮助,但由于保证了它们的行为并不多,它们不适合成为您的主要资源管理机制.
小智 6
Go中有Finalizer.我写了一篇关于它的博文.他们甚至用于关闭标准库中的文件,如此处所示.但是我认为使用延迟更为可取,因为它更具可读性且不那么神奇.