去破坏者?

时间:2015-09-24 18:16:45

标签: go

我知道Go中没有析构函数,因为从技术上讲没有类。因此,我使用initClass来执行与构造函数相同的功能。但是,有没有办法在终止时创建一些模仿析构函数的东西,比如关闭文件?现在我只是打电话给defer deinitClass,但这是相当hackish我觉得设计很差。什么是正确的方法?

3 个答案:

答案 0 :(得分:30)

在Go生态系统中,存在一种无处不在的习惯用法,用于处理包裹珍贵(和/或外部)资源的对象:指定用于释放该资源的特殊方法,通常称为 defer机制。

此特殊方法通常命名为Close(),并且对象的用户在完成对象所代表的资源时必须显式调用它。 io标准包甚至有一个特殊的接口io.Closer,声明了这个单一的方法。在各种资源(如TCP套接字,UDP端点和文件)上实现I / O的对象都满足io.Closer,并且在使用后应该明确Close

调用这样的清理方法通常是通过defer机制完成的,该机制保证无论在资源获取后执行的某些代码是panic()还是delete,该方法都会运行。

您可能还注意到,没有隐含的“析构函数”可以完全平衡Go中没有隐式“构造函数”。这实际上与Go中没有“类”无关:语言设计者尽可能地避免使用 magic

请注意,Go解决此问题的方法可能似乎有点低技术,但实际上它是运行时具有垃圾收集功能的唯一可行解决方案。在具有对象但没有GC的语言中,比如说C ++,破坏对象是一个定义明确的操作,因为对象在超出范围或在其内存块上调用IDisposable时会被销毁。在使用GC的运行时,GC扫描将来会在某些大多数不确定的位置销毁该对象,并且可能根本不会被销毁。因此,如果对象包含一些宝贵的资源,那么该资源可能根本没有得到回收,正如@twotwotwo在他们各自的回答中所解释的那样。

如果您熟悉.NET,它会以类似于Go的方式处理资源清理:包装一些宝贵资源的对象必须实现Dispose()接口和方法,当您完成此类对象时,必须显式调用由该接口导出的using。 C#通过Dispose()语句为这个用例提供了一些语法糖,这使得编译器在超出上述语句声明的作用域时安排在对象上调用defer。在Go中,您通常会os.File调用清理方法。

还有一点需要注意。 Go希望您非常认真地对待错误(与大多数主流编程语言"just throw an exception and don't give a fsck about what happens due to it elsewhere and what state the program will be in" attitude不同),因此您可能会考虑检查至少某些调用清理方法的错误返回。

一个很好的例子是表示文件系统上文件的Close()类型的实例。有趣的是,由于合法的原因,在打开的文件上调用fd, err := os.Open("foo.txt") defer fd.Close() 可能失败,如果您到该文件,这可能表明并非所有您写入该文件的数据实际上已落在文件系统上的有关说明,请阅读close(2) manual中的“注释”部分。

换句话说,就像做

之类的事情
save()

对于99.9%的情况下的只读文件是可以的,但对于开放写入的文件,您可能希望实现更多涉及的错误检查和一些处理它们的策略(仅仅是报告,等待然后重试,问 - 然后 - 可能 - 重试或其他)。

答案 1 :(得分:12)

runtime.SetFinalizer(ptr, finalizerFunc)设置终结器 - 不是析构函数,而是另一种可能最终释放资源的机制。阅读那里的文档了解详细信息,包括缺点。它们可能在对象实际无法访问之后才运行,如果程序首先退出,它们可能根本不运行。他们还推迟释放内存以进行另一个GC循环。

如果您正在获取一些尚未拥有终结器的有限资源,并且如果程序不断泄漏,程序最终将无法继续,您应该考虑设置终结器。它可以减少泄漏。 stdlib中的终结器已经清除了无法访问的文件和网络连接,因此它只是其他类型的资源,其中自定义的资源可能很有用。最明显的类是您通过syscallcgo获得的系统资源,但我可以想象其他人。

虽然他们可以修补一些错误,但你不应该依赖在终结者身上。它们不会运行直到GC运行。因为程序可以在下一个GC之前退出,所以你不能依赖它们来做必须完成的事情,比如将缓冲的输出刷新到文件系统。假设GC 发生,可能不会很快发生:如果终结器负责关闭网络连接,可能远程主机在GC之前打开与您的连接的限制,或者您的进程命中其文件-descriptor limit,或者你用完ephemeral ports或其他东西。所以defer要好得多,并且在必要的情况下立即清理,而不是使用终结器并希望尽快完成。

你在日常的Go编程中没有看到很多SetFinalizer个调用,部分原因是最重要的调用是在标准库中,主要是因为它们的适用范围有限。

简而言之,终结者可以通过在长期运行的程序中释放被遗忘的资源来提供帮助,但由于保证他们的行为并不多,所以他们不适合成为您的主要资源管理机制。

答案 2 :(得分:5)

Go中有Finalizer。我写了很少blog post。他们甚至用于关闭标准库中的文件,因为您可以看到here。 但是我认为使用延迟更为可取,因为它更具可读性且不那么神奇。