在关于将 Perl 5 代码迁移到 Perl 6 的系列第一篇文章中,我们研究了移植代码时可能遇到的一些问题。在第二篇文章中,我们将深入探讨 Perl 6 中垃圾回收的不同之处。
Perl 6 中没有对象的及时销毁。这个发现通常会给习惯于 Perl 5 中对象销毁语义的人们带来相当大的冲击。但是请不要担心,Perl 6 中还有其他方法可以获得相同的行为,尽管这需要开发人员多加思考。让我们首先研究一下 Perl 5 中的情况背景。
引用计数
在 Perl 5 中,“超出作用域”的对象的及时销毁是通过引用计数实现的。当在 Perl 5 中创建某些内容时,它具有 1 个或多个引用计数,这使其保持活动状态。在最简单的情况下,它看起来像这样
{
my $a = 42; # reference count of $a = 1, because lives in lexical pad
}
# lexical pad is gone, reference count to 0
在 Perl 5 中,如果该值是一个对象(也称为 blessed),则将对其调用 DESTROY
方法。
{
my $a = Foo->new;
}
# $a->DESTROY called
如果没有涉及外部资源,及时销毁只是管理程序使用的内存的另一种方式。作为程序员,您无需关心事物如何以及何时被回收。话虽如此,如果您需要处理外部资源(例如数据库句柄,数据库服务器通常只提供有限数量的数据库句柄),那么及时销毁是一个非常好的功能。而引用计数可以提供这一点。
但是,引用计数有几个缺点。Perl 5 核心开发人员花费了多年时间才使引用计数正确工作。如果您在 XS 中工作,则始终需要注意引用计数,以防止内存泄漏或过早销毁。
在多线程环境中,保持事物同步变得更加困难,因为您不希望丢失来自多个线程同时进行的引用更新(因为这会导致内存泄漏和/或外部资源无法释放)。为了规避这种情况,需要某种形式的锁定或原子更新,但这两种方法都不便宜。
请注意,Perl 5 ithreads 更像是解释器之间内存不共享的内存内 fork,而不是 C 等编程语言中的线程。因此,它仍然不需要任何锁定来进行引用计数。
引用计数还具有基本缺点,即如果两个对象包含对彼此的引用,它们将永远不会被销毁,因为它们使彼此的引用计数保持在 0 以上(循环引用)。在实践中,这通常会更深入,更像是 A -> B -> C -> A
,其中 A、B 和 C 都使彼此保持活动状态。
弱引用的概念是为了规避 Perl 5 中的这些情况而开发的。虽然这可以解决循环引用问题,但它具有性能影响,并且无法解决首先拥有(和查找)循环引用的问题。您需要能够找出在哪里可以以最佳方式使用弱引用;否则,您可能会遇到不必要的过早对象销毁。
可达性分析
由于 Perl 6 在其核心中是多线程的,因此在非常早期的阶段就决定,引用计数在性能和维护方面都将存在问题。相反,当需要更多内存并且可以安全删除对象时,对象将从内存中逐出。
在 Perl 6 中,您可以创建 DESTROY
方法,就像在 Perl 5 中一样。但是您不能确定它何时(如果曾经)会被调用。
在不深入 太多细节的情况下,Perl 6 中的对象仅在启动垃圾回收运行时才会被销毁,例如,当达到某个内存限制时。只有那时,如果内存中的其他对象不再可访问某个对象,并且它具有 DESTROY
方法,它才会在对象被删除之前被调用。
当程序退出时,Perl 6 不会进行垃圾回收。适用的 phasers(例如 LEAVE
和 END
)将被调用,但不会进行垃圾回收,除非由 phasers 中运行的代码(间接)启动。
如果您始终需要有序地关闭程序使用的外部资源(例如数据库句柄),则可以使用 phaser 来确保以正确和及时的方式释放外部资源。
例如,您可以使用 END
phaser(在 Perl 5 中称为 END
块)在程序退出时(无论出于何种原因)正确断开与数据库的连接
my $dbh = DBIish.connect( ... ) or die "Couldn't connect";
END $dbh.disconnect;
请注意,END
phaser 在 Perl 6 中不需要具有块(例如 { ... }
)。如果它没有,则 phaser 中的代码与周围的代码共享词法 pad (lexpad)。
上面的代码中有一个缺陷:如果程序在之前建立数据库连接退出,或者如果数据库连接由于任何原因失败,它仍然会尝试对 $dbh
中的任何内容调用 .disconnect
方法,这将导致执行错误。但是,Perl 6 中有一种简单的习语可以规避这种情况,使用 with。
END .disconnect with $dbh;
后缀 with
仅在给定值已定义(通常是实例化的对象)时才匹配,然后将其主题化为 $_
。.disconnect
是 $_.disconnect
的简写。
如果您希望在退出特定作用域时清理外部资源,则可以在该作用域内使用 LEAVE
phaser。
if DBIish.connect( ... ) -> $dbh {
LEAVE $dbh.disconnect; # no need for `with` here
# do your stuff with the database
}
else {
say "Could not do the stuff that needed to be done";
}
每当 if
的作用域被离开时,任何 LEAVE
phaser 都将被执行。因此,无论何时代码在该作用域中运行,数据库资源都将被释放。
总结
尽管 Perl 6 没有 Perl 5 用户习惯的及时对象销毁,但它确实有易于使用的替代方法来确保外部资源的管理,类似于 Perl 5 中的方法。
3 条评论