Perl 6 中的垃圾回收

在本系列比较 Perl 5 和 Perl 6 的第二篇文章中,了解如何在 Perl 6 中处理对象销毁。
194 位读者喜欢这篇文章。
Garbage illustration

Internet Archive Book Images。由 Opensource.com 修改。CC BY-SA 4.0

在关于将 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(例如 LEAVEEND被调用,但不会进行垃圾回收,除非由 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 中的方法。

Elizabeth Mattijsen
Elizabeth Mattijsen 自 1978 年以来一直从事编程工作,使用各种(现在大多已废弃的)编程语言,之后她开始使用 Perl 4 编程。1994 年,她创立了荷兰第一家商业网站开发公司,使用 Perl 5 作为主要编程语言。从 2003 年起,她参与了一家在线酒店预订服务的快速增长。

3 条评论

嗨 Elizabeth,

关于这篇文章写得真好,我是一名学生,我发现这段有点难以理解。

"由于 Perl 6 在其核心中是多线程的,因此在非常早期的阶段就决定,引用计数在性能和维护方面都将存在问题。相反,当需要更多内存并且可以安全删除对象时,对象将从内存中逐出。"

我错过了什么吗?

此致,
Ethan
https://www.mybkexperience.onl
https://www.krogerfeedback.onl

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.