猿问

去破坏者?

我知道 Go 中没有析构函数,因为技术上没有类。因此,我initClass用来执行与构造函数相同的功能。但是,有没有办法在终止的情况下创建一些东西来模拟析构函数,例如用于关闭文件?现在我只是调用defer deinitClass,但这是相当骇人听闻的,我认为这是一个糟糕的设计。什么是正确的方法?


临摹微笑
浏览 193回答 3
3回答

慕无忌1623718

在 Go 生态系统中,存在一种普遍存在的习惯用法来处理包装珍贵(和/或外部)资源的对象:一种专门用于释放该资源的特殊方法,显式调用——通常通过defer机制。这个特殊的方法通常被命名为Close(),并且对象的用户在完成对象所代表的资源时必须显式调用它。该io标准包中甚至有专门的接口,io.Closer,宣称单一的方法。在 TCP 套接字、UDP 端点和文件等各种资源上实现 I/O 的对象都满足io.Closer,并且预计Close在使用后显式d。调用这样的清理方法通常是通过defer保证该方法将运行的机制来完成的,无论在资源获取后执行的某些代码是否会执行panic()。您可能还会注意到,在 Go 中没有隐式“析构函数”与没有隐式“构造函数”相当平衡。这实际上与 Go 中没有“类”无关:语言设计者只是尽可能地避免使用魔法。请注意,Go 解决此问题的方法可能看起来技术含量较低,但实际上它是具有垃圾收集功能的运行时唯一可行的解决方案。在有对象但没有 GC 的语言中,比如 C++,销毁一个对象是一个定义明确的操作,因为一个对象在超出范围或delete在其内存块上被调用时被销毁。在带有 GC 的运行时中,对象将在将来的某个几乎不确定的点被 GC 扫描销毁,并且可能根本不会被销毁。 因此,如果对象包装了一些宝贵的资源,那么该资源可能会在最后一次对封闭对象的实时引用丢失之后被回收,甚至可能根本不会被回收——正如@twotwotwo 在他们各自的回答。另一个需要考虑的有趣方面是 Go 的 GC 是完全并发的(与常规程序执行)。这意味着将要收集死对象的 GC 线程可能(并且通常不会)不是在该对象处于活动状态时执行该对象代码的线程。反过来,这意味着如果 Go 类型可以有析构函数,那么程序员需要确保析构函数执行的任何代码都与程序的其余部分正确同步——如果对象的状态影响它外部的一些数据结构。这实际上可能会迫使程序员添加此类同步,即使对象的正常操作不需要它(并且大多数对象都属于此类)。想想那些在对象之前碰巧被破坏的外部数据结构会发生什么” s 析构函数被调用(GC 以不确定的方式收集死对象)。换句话说,当对象销毁被明确编码到程序流中时,它更容易控制和推理:既用于指定何时必须销毁对象,也用于保证销毁的正确顺序。它外部的数据结构。如果您熟悉 .NET,它以一种非常类似于 Go 的方式处理资源清理:包装一些宝贵资源的对象必须实现该IDisposable接口,并且该接口Dispose()导出的方法必须是当你完成这样一个对象时显式调用。C# 通过using语句为这个用例提供了一些语法糖,Dispose()当对象超出由所述语句声明的范围时,编译器会安排对对象进行调用。在 Go 中,您通常会defer调用清理方法。还有一个注意事项。Go 希望您非常认真地对待错误(与大多数主流编程语言不同,它们的“只是抛出一个异常,而不是在其他地方因为它而发生什么以及程序将处于什么状态”的态度),所以您可能考虑检查至少一些清理方法调用的错误返回。一个很好的例子是os.File表示文件系统上文件的类型的实例。有趣的是,由于合法原因调用Close()打开的文件可能会失败,如果您正在写入该文件,这可能表明并非您写入该文件的所有数据实际上都已进入文件系统。 有关解释,请阅读close(2)手册中的“注意事项”部分。换句话说,只是做类似的事情fd, err := os.Open("foo.txt")defer fd.Close()在 99.9% 的情况下,只读文件是可以的,但是对于为写入而打开的文件,您可能需要实现更多涉及的错误检查和一些处理它们的策略(仅报告、等待然后重试、询问然后-也许-重试或其他)。

喵喔喔

runtime.SetFinalizer(ptr, finalizerFunc)设置终结器——不是析构函数,而是另一种可能最终释放资源的机制。阅读那里的文档以了解详细信息,包括缺点。它们可能要在对象实际上无法访问之后很久才会运行,并且如果程序先退出,它们可能根本不会运行。他们还推迟为另一个 GC 周期释放内存。如果您正在获取一些没有终结器的有限资源,并且如果程序不断泄漏,程序最终将无法继续,您应该考虑设置终结器。它可以减少泄漏。无法访问的文件和网络连接已经由 stdlib 中的终结器清除,因此只有其他类型的资源可以使用自定义资源。最明显的类是您通过syscallor获取的系统资源cgo,但我可以想象其他类。虽然它们可以修补一些错误,但您不应该依赖终结器。在 GC 执行之前,它们不会运行。因为程序可能会在下一次 GC 之前退出,所以您不能依赖它们来完成必须完成的事情,例如将缓冲输出刷新到文件系统。假设 GC确实发生了,它可能不会很快发生:如果终结器负责关闭网络连接,则可能是远程主机在 GC 之前达到了与您的打开连接的限制,或者您的进程达到了其文件描述符限制,或者您运行出于临时端口或其他原因。因此,在必要时立即defer进行清理比使用终结器并希望它尽快完成要好得多。SetFinalizer在日常 Go 编程中你不会看到很多调用,部分原因是最重要的调用在标准库中,主要是因为它们的一般适用范围有限。简而言之,终结器可以通过释放长时间运行的程序中被遗忘的资源来提供帮助,但由于它们的行为没有多少保证,因此它们不适合作为您的主要资源管理机制。

开满天机

Go 中有终结器。我写了一篇关于它的小博客文章。他们甚至用于标准库关闭文件,你可以看到在这里。但是,我认为使用 defer 更可取,因为它更具可读性且不那么神奇。
随时随地看视频慕课网APP

相关分类

Go
我要回答