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