从另一个递归调用一个匿名子是否安全?
Is it safe to recursively call one anonymous sub from another?
我想使用匿名 subs 而不是命名 subs 的原因是因为我想在 Mason 子组件 (http://www.masonbook.com/book/chapter-2.mhtml#TOC-ANCHOR-7) 中定义这些 subs,它们与命名 subs 的表现不佳。
例如如果我这样写代码:
my ($first, $second);
$first = sub {
my $val = shift;
print "val: $val";
$second->($val);
};
$second = sub {
my $val = shift;
if (0 < $val) {
$val = $val - 1;
$first->($val);
}
};
$first->(10);
这种方法是否存在任何隐藏的陷阱(例如内存泄漏等)?
正如@Schwern 所解释的,Perl 不会释放这些子程序的内存,因为它们之间存在循环引用。
但更具体地说,内存分配会随着 $val 的增加而线性增长,还是不依赖于调用堆栈深度?因为我可以把这些潜艇放在 mason <%once> 块中,这样的话这些潜艇只会被初始化一次。
以下可以:
sub first {
my $val = shift;
print "val: $val";
second($val);
}
sub second {
my $val = shift;
if (0 < $val) {
$val = $val - 1;
first($val);
}
}
first(10);
唯一需要注意的是,如果子项有原型,或者您希望省略参数周围的括号,则需要声明子项。
sub first($);
sub second($);
sub first($) {
my $val = shift;
print "val: $val";
second $val;
}
sub second($) {
my $val = shift;
if (0 < $val) {
$val = $val - 1;
first $val;
}
}
first 10;
另一方面,您的版本存在内存泄漏。第一个子捕获对第二个子的引用,第二个子捕获对第一个子的引用。
$ perl -e'
sub DESTROY { print "Destroyed\n" }
{
my ($first, $second);
$first = sub { $second };
$second = sub { $first };
bless($first);
}
print("Subs should have been destroyed by now\n");
'
Subs should have been destroyed by now
Destroyed
解决方案取决于您最初决定使用匿名订阅的原因。
我唯一能想到的是子例程永远不会被释放,即使 $first
和 $second
超出范围。 $first
的代码是指$second
,$second
的代码是指$first
。这是一个循环数据结构,Perl 的内存分配无法释放它。
$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub {}; $second = sub {} } say "Done"; sleep 1000'
$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() } } say "Done"; sleep 1000'
循环后第一个 Perl 进程使用 1912K,第二个使用 10320K。无论创建多少 CV,第一个不会增长,第二个会。
要解决这个问题,您必须通过取消定义 $first
或 $second
来打破这个循环。第三个在循环内调用 undef $first
,它的内存不会增长。
$ perl -wlE 'for (1..100_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() }; undef $first; } say "Done"; sleep 1000'
我想使用匿名 subs 而不是命名 subs 的原因是因为我想在 Mason 子组件 (http://www.masonbook.com/book/chapter-2.mhtml#TOC-ANCHOR-7) 中定义这些 subs,它们与命名 subs 的表现不佳。
例如如果我这样写代码:
my ($first, $second);
$first = sub {
my $val = shift;
print "val: $val";
$second->($val);
};
$second = sub {
my $val = shift;
if (0 < $val) {
$val = $val - 1;
$first->($val);
}
};
$first->(10);
这种方法是否存在任何隐藏的陷阱(例如内存泄漏等)?
正如@Schwern 所解释的,Perl 不会释放这些子程序的内存,因为它们之间存在循环引用。
但更具体地说,内存分配会随着 $val 的增加而线性增长,还是不依赖于调用堆栈深度?因为我可以把这些潜艇放在 mason <%once> 块中,这样的话这些潜艇只会被初始化一次。
以下可以:
sub first {
my $val = shift;
print "val: $val";
second($val);
}
sub second {
my $val = shift;
if (0 < $val) {
$val = $val - 1;
first($val);
}
}
first(10);
唯一需要注意的是,如果子项有原型,或者您希望省略参数周围的括号,则需要声明子项。
sub first($);
sub second($);
sub first($) {
my $val = shift;
print "val: $val";
second $val;
}
sub second($) {
my $val = shift;
if (0 < $val) {
$val = $val - 1;
first $val;
}
}
first 10;
另一方面,您的版本存在内存泄漏。第一个子捕获对第二个子的引用,第二个子捕获对第一个子的引用。
$ perl -e'
sub DESTROY { print "Destroyed\n" }
{
my ($first, $second);
$first = sub { $second };
$second = sub { $first };
bless($first);
}
print("Subs should have been destroyed by now\n");
'
Subs should have been destroyed by now
Destroyed
解决方案取决于您最初决定使用匿名订阅的原因。
我唯一能想到的是子例程永远不会被释放,即使 $first
和 $second
超出范围。 $first
的代码是指$second
,$second
的代码是指$first
。这是一个循环数据结构,Perl 的内存分配无法释放它。
$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub {}; $second = sub {} } say "Done"; sleep 1000'
$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() } } say "Done"; sleep 1000'
循环后第一个 Perl 进程使用 1912K,第二个使用 10320K。无论创建多少 CV,第一个不会增长,第二个会。
要解决这个问题,您必须通过取消定义 $first
或 $second
来打破这个循环。第三个在循环内调用 undef $first
,它的内存不会增长。
$ perl -wlE 'for (1..100_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() }; undef $first; } say "Done"; sleep 1000'