【问题标题】:Unexpected behavior in nested recursive function嵌套递归函数中的意外行为
【发布时间】:2015-08-03 14:07:58
【问题描述】:

我有一些行为相当奇怪的代码。

我在一个函数内部,我声明了一个嵌套函数,它应该检查是否有问题。如果不是,那么它应该休眠五秒钟,然后再次调用自己。

sub stop {

    sub wait_for_stop {
        my $vm_view = shift;

        if ( $vm_view->runtime->powerState->val ne "poweredOff" ) {
            debug("...");
            sleep(5);
            wait_for_stop();
        }
    }

    debug("Waiting for the VM to stop");
    wait_for_stop( @$vm_views[0] );
}

因此,在导致if 条件内递归的调用中,如果我输入参数(如函数定义所期望的那样),如下所示:

wait_for_stop($vm_view);

我得到一个无限循环。

如果我不带参数,就像上面的代码示例一样,它会按预期工作。

后续调用中的$vm_view不应该为空吗?还是最后使用的值 ($vm_view->runtime->powerState->val)?这两种情况都会导致意外的行为和错误。

但它可以在没有任何参数的情况下工作。那为什么呢? perldoc 有什么我遗漏的吗?

EDIT1: 实际上,$vm_views 的值确实发生了变化,所以这不是无限循环的原因。

一般说明

我正在使用 VMware SDK。 $vm_views 对象包含 VM 详细信息。我正在轮询它的一种方法来检测变化,在这种特殊情况下,我需要知道机器何时关闭。所以,由于没有更好的方法,我每 5 秒打一次电话,直到数值满意为止。

我的目的是停止虚拟机,进行只能在关闭时进行的修改,然后启动它。

实际问题

当我不传递参数时,该块按预期工作——它会等到值是poweredOff(VM 关闭),然后继续,至少对我来说没有多大意义。

如果我将$vm_view 作为参数,我会得到一个无限循环(值仍然会改变,因为我正在调用一个方法)。

所以我想知道为什么该函数有效,第一次调用后,$vm_view 应该是undef,因此会陷入无限循环? [undef ne "poweredOff" -> sleep -> 递归直到死亡]

为什么,当我通过期望值时,它会卡住?

PS:对于那些说我的递归在这种情况下很奇怪和无用的人——由于种种原因,我需要使用这种格式(它更适合我的需要,因为在我开始工作之后,我将对其进行修改以添加各种东西并重用它,而且,就我的想法而言,函数似乎是最好的选择)。

【问题讨论】:

  • “它按预期工作”这是否意味着当你不传入参数时它不会给出无限循环?
  • 你能提供一个MCVE,也许还有一个简单的嘲笑$vm_view
  • 这是递归的一种奇怪用法。我认为while ($val ne "poweredOff") { sleep 5; } 一目了然更容易理解,并且您不必担心深度递归警告或进栈。 (当然,你必须用$val 实际 一些事情,否则你会得到一个无限循环,无论你使用递归还是迭代。)
  • "undef ne "poweredOff" -> sleep -> recursion until death" 这不是发生的事情。 $vm_view 的值为 undef,但您尝试调用其 runtime 方法。在接近比较之前,Can't call method "runtime" on an undefined value 将立即失败。这不能是您正在运行的代码
  • 关于递归的选择,请相信我是错误的选择。有总是一种避免递归的方法,这里它只是代表while 循环。每次递归过程调用自己时,都会用完另一个堆栈帧,如果等待时间过长,您就有可能遇到OUT OF MEMORY! 错误,这将杀死您的程序。除了难以阅读之外,而且(正如您所发现的)难以正确理解

标签: perl recursion while-loop vmware


【解决方案1】:

在使用递归等更奇特的东西之前,您应该始终查看您的标准工具。这里只需要一个while 循环

还值得注意的是,@$vm_views[0] 应该是 $$vm_views[0]),或者更好的是 $vm_views->[0]。而且在内部另外定义一个子程序并没有任何收获——效果和之后单独声明的效果是一样的

如果$vm_view->runtime->powerState->val 永远不会返回poweredOff,我会期望出现无限循环,而下面的代码将无法解决这个问题。在您等待状态更改之前,我没有看到任何告诉 VM 停止的代码。对吗?

我不明白为什么你说在没有任何参数的情况下调用wait_for_stop 时你的代码可以正常工作。您将收到致命错误

Can't call method "runtime" on an undefined value

您的程序将停止。是你贴出的真实代码吗?

这将达到您的预期。我也认为它更容易阅读

use strict;
use warnings;

my $vm_views;

sub stop {

    debug ("Waiting for the VM to stop");

    my $vm_view = $vm_views->[0];
    while ( $vm_view->runtime->powerState->val ne 'poweredOff' ) {
        debug('...');
        sleep 5;
    }
}

【讨论】:

  • 关于“另外,通过在另一个子程序中定义子程序不会获得任何好处”,我能想到一个:头痛。这确实是个坏主意。
【解决方案2】:

我认为您最好不要递归调用wait_for_stop()。这种方式可能会更好地为您服务:

sub stop
{
    sub wait_for_stop
    {
            my $vm_view = shift;
            if ($vm_view->runtime->powerState->val ne "poweredOff")
            {
                    debug("...");
                    #sleep(5);
                    #wait_for_stop();
                    return 0;
            }
            return 1;
    }
    debug ("Waiting for the VM to stop");
    until(wait_for_stop(@$vm_views[0]))
    {
         sleep(5);
    }
}

您的旧方法相当混乱,我认为您没有将 $vm_view 变量传递给递归子例程调用。

【讨论】:

  • OP 不希望你改进代码,他希望你解释他所看到的反直觉行为。
【解决方案3】:

更新:

我试着在这里阅读它: https://www.vmware.com/support/developer/viperltoolkit/doc/perl_toolkit_guide.html#step3

上面写着:

当脚本运行时,VI Perl Toolkit 运行时会检查 环境变量、配置文件内容和命令行 条目(按此顺序)以获取必要的连接设置详细信息。如果 这些覆盖都不可用,运行时使用默认值 (参见表 1)来设置连接。

那么,即使没有定义 vm 对象,“运行时”也会使用默认连接详细信息?或许? 那仍然没有回答为什么在传递参数时它不起作用。 您需要更好地了解 VM SDK。您的递归逻辑和函数参数的使用都很好。

另外,页面:https://www.vmware.com/support/developer/viperltoolkit/doc/perl_toolkit_guide.html

说——

VI Perl Toolkit 视图有几个你应该具备的特征 在编写自己的脚本时请记住。具体来说,一个观点:

  • 是一个 Perl 对象

  • 包括与属性和相关的属性和方法 服务器端管理对象的操作

  • 是一个或多个服务器端托管对象的静态副本,并且作为 这样(静态),不会自动更新为对象的状态 服务器上的变化。

所以“vm”函数返回的是一个静态副本,可以从脚本中更新。当您在传递 $vm_view 时拨打电话时可能会更新?

旧答案: 问题不是您从 Perl 文档中遗漏的内容。问题在于您对递归的理解。

递归的目的是一直运行直到$vm_view->runtime->powerState->val变为“PoweredOff”,然后级联回来。如果您不更新该值,它将永远运行。

当你说:

我得到一个无限循环。

您是否在 if 条件内更新 $vm_view? 否则,每次调用函数时变量都是相同的,因此可能会陷入无限循环。

如果我不带参数,如上面的代码示例,它 按预期工作。

它如何按预期工作?期望什么?该函数无法知道您的 $vm_view 正在使用什么值进行更新。

我已经简化了代码,添加了更新一个简单的变量(类似于您的 $vm_view)并进行了测试。使用它来了解正在发生的事情:

sub wait_for_stop
{
    my $vm_view = shift;

    if ($vm_view < 10){
        print "debug...\n";
        sleep(5);
        $vm_view++; // update $vm_view->runtime->powerState->val here
                    // so that it becomes "poweredOff" at some point of time
                    // and breaks the recursion
        wait_for_stop($vm_view);
    }
}

wait_for_stop(1);

在 cmets 中告诉我变量是如何更新的,我将帮助解决。

【讨论】:

  • "否则,每次调用函数时变量都是相同的,因此您可能会陷入无限循环。" $vm_view-&gt;runtime-&gt;powerState-&gt;val 在每次迭代中不一定具有相同的值。 val 是一种方法,因此它可能会在每次调用时返回不同的值,例如 return time();return rand();
  • 当然,如果$vm_view未定义,则根本不会调用任何方法。
  • @ThisSuitIsBlackNot 是正确的,我正在调用一个方法,所以值会改变(最终),我试图用我的代码来捕捉。至于“预期”是什么,我试图检测该值何时更改为我需要的值(powerOff),因此我每 5 秒调用一次该方法。只有当我传递参数时才会发生无限循环。当值为 undef(没有参数传递给函数)/ PS:检查帖子上的更新时,它可以工作
  • 原谅我这么狭隘。我添加了一个更新。感谢@ThisSuitIsBlackNot,纠正我的观点。
  • @Chandar Kakarlapudi SDK 文档可能有点误导。我已经执行了检查(在每个循环上都进行了检查)并且值确实得到了更新。问题是,在它完成之后,什么都没有发生(它只是挂在那里并且不再继续,也不再循环 - 我误以为我有一个无限循环,我实际上有一个很长的循环然后什么都没有)跨度>
猜你喜欢
  • 2019-06-13
  • 1970-01-01
  • 2021-05-01
  • 2017-07-11
  • 2014-08-07
  • 2016-08-08
  • 1970-01-01
  • 2021-04-21
  • 1970-01-01
相关资源
最近更新 更多