我知道Go语言中没有析构函数,因为从技术上讲,它没有类。因此,我使用initClass
来执行与构造函数相同的功能。但是,在终止时是否有任何方法可以创建类似于析构函数的东西,以便用于关闭文件等用途?目前,我只是调用defer deinitClass
,但这样做有点欺骗性,我认为这是一个不良的设计。正确的方式是什么?
我知道Go语言中没有析构函数,因为从技术上讲,它没有类。因此,我使用initClass
来执行与构造函数相同的功能。但是,在终止时是否有任何方法可以创建类似于析构函数的东西,以便用于关闭文件等用途?目前,我只是调用defer deinitClass
,但这样做有点欺骗性,我认为这是一个不良的设计。正确的方式是什么?
defer
机制实现。Close()
,对象的用户必须在使用完对象所代表的资源后显式调用它。甚至 io
标准包都有一个特殊的接口,io.Closer
,声明了这个单一的方法。实现各种资源上的 I/O,如 TCP sockets、UDP 端点和文件的对象都满足 io.Closer
,并且在使用后应该显式地进行 Close
。defer
机制完成的,该机制保证无论在获取资源后执行的某些代码是否会 panic()
,方法都将运行。还有一点需要注意的是,Go语言要求您非常严肃地对待错误(与大多数主流编程语言不同,它们采用“只需抛出异常,不关心其他地方会发生什么以及程序将处于什么状态”的态度),因此您可能需要考虑检查至少一些清理方法的错误返回。
一个很好的例子是代表文件系统上的文件的os.File
类型的实例。 有趣的是,调用打开文件的Close()
可能会由于合法原因而失败,如果您正在向该文件写入,这可能表明您写入到该文件的并非全部真正落在了文件系统中。 有关说明,请阅读close(2)
手册中的“注释”部分。
换句话说,仅仅做类似以下操作是不够的:
fd, err := os.Open("foo.txt")
defer fd.Close()
对于只读文件,在99.9%的情况下使用是可以的,但对于打开写入权限的文件,您可能需要实现更复杂的错误检查和一些处理策略(仅报告、等待再重试、询问再或许重试或其他)。
runtime.SetFinalizer(ptr, finalizerFunc)
设置一个finalizer--不是析构函数,而是另一种可能最终释放资源的机制。详见文档,包括其缺点。它们可能在对象实际上已经无法访问之后很长时间才运行,并且如果程序先退出,则它们可能根本不会运行。它们还会延迟到下一个垃圾回收周期来释放内存。
如果你正在获取一些有限的资源,该资源尚未具备finalizer,而且如果它持续泄漏,程序最终将无法继续运行,那么应该考虑设置finalizer。它可以减少泄漏。在stdlib中,不可访问的文件和网络连接已由finalizer清理,因此只有其他类型的资源才需要自定义finalizer。其中最明显的类是通过syscall
或cgo
获取的系统资源,但我也可以想象其他类型的资源。
Finalizer可以帮助最终释放资源,即使使用它的代码省略了Close()
或类似的清理操作,但它们太不可预测了,不能成为释放资源的主要方式。它们只有在GC运行时才会运行。因为程序可能在下一个GC之前退出,所以你不能依赖它们来完成必须要做的事情,比如将缓冲输出刷新到文件系统中。如果GC确实发生了,它可能不会及时发生:如果finalizer负责关闭网络连接,则可能在GC之前远程主机已达到其打开到您的连接的限制,或者您的进程达到了其文件描述符限制,或者您用尽了瞬态端口,或者其他某些原因。因此,与使用finalizer并希望它能及时完成处理相比,更好的方法是使用defer
和在必要时立即进行清理。
在日常的Go编程中,你很少看到SetFinalizer
调用,部分原因是其中最重要的调用已经在标准库中,大多数原因是它们适用范围有限。
简而言之,终结器可以在长时间运行的程序中释放被遗忘的资源,但由于它们的行为没有太多保证,它们不适合作为您主要的资源管理机制。
SetFinalizer
是唯一可用的方法,可以在其golang依赖项被垃圾回收时释放cgo资源 - 对于c包装器非常有用,特别是当C对象相对于彼此的释放时间存在限制时。这应该是被接受的答案。 - domoarigatodefer
立即释放资源,而不是依赖于终结器。换句话说,终结器可能是一个有用的保险机制,可以防止难以弥补的泄漏,但它们不适合作为主要的资源管理机制。 - twotwotwo
New()
或NewWhatever()
:它们给你一个新的初始化值。 - kostix