【问题标题】:Is it safe to recursively call one anonymous sub from another?从另一个匿名子递归调用一个匿名子是否安全?
【发布时间】:2015-12-27 09:02:41
【问题描述】:

我想使用匿名子组件而不是命名子组件的原因是因为我想在 Mason 子组件 (http://www.masonbook.com/book/chapter-2.mhtml#TOC-ANCHOR-7) 中定义这些子组件,这些子组件在命名子组件中表现不佳。

例如如果我这样写代码:

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 不会释放这些 subs 的内存,因为它们之间存在循环引用。

但更具体地说,内存分配会随着 $val 的增加而线性增长,还是不依赖于调用堆栈深度?因为我可以将这些 subs 放在 mason 块中,在这种情况下,这些 subs 只会被初始化一次。

【问题讨论】:

    标签: perl mason


    【解决方案1】:

    以下就可以了:

    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);
    

    唯一需要注意的是,如果 subs 有原型,或者您希望省略参数周围的括号,则需要声明它们。

    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;
    

    另一方面,您的版本存在内存泄漏。第一个 sub 捕获对第二个 sub 的引用,第二个 sub 捕获对第一个 sub 的引用。

    $ 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
    

    解决方案取决于您首先决定使用匿名潜艇的原因。

    【讨论】:

      【解决方案2】:

      我唯一能想到的是,即使$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'
      

      【讨论】:

      • 谢谢大家!但基本上,如果我在循环之外定义匿名子,我的内存占用不会增加?
      • 循环只会夸大问题;您仍然必须取消定义 $first 或 $second 函数以避免循环结构。
      • @Yakov 循环只是为了说明泄漏。它 A) 提供了一个词法上下文(即块),否则子例程在退出时会被破坏,并且 B) 夸大了内存泄漏,因此它会出现在像 ps 这样的粗略工具中。
      • @Yakov,如果代码不在某种循环中,那么很难想象为什么需要匿名潜艇。同样,如果需要匿名潜艇,则可能无法使用 Schwern 的释放它们的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-11-10
      • 2018-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多