【问题标题】:How can I limit the time spent in a specific section of a Perl script?如何限制在 Perl 脚本的特定部分花费的时间?
【发布时间】:2009-07-22 13:38:43
【问题描述】:

有没有办法建立一些时间计数器,使脚本的某些部分只要滴答作响就可以运行吗?例如,我有以下代码:

for my $i (0 .. $QUOTA-1) {
    build_dyna_file($i);
    comp_simu_exe;
    bin2txt2errormap($i);
}

理论上我想运行这个循环 3 分钟,即使循环指令还没有完成,它仍然应该在 3 分钟后跳出循环。

实际上,程序会打开一个计时窗口,该窗口与部分脚本并行工作(每次我调用它时)。

此外,子调用“comp_simu_exe”运行一个外部模拟器(在 shell 中),当超时结束时 - 这个进程也必须终止(不应该在一段时间后返回)。

sub comp_simu_exe{

system("simulator --shell");
}

死机问题与系统函数调用之间有什么联系吗?

【问题讨论】:

    标签: perl timeout


    【解决方案1】:

    您可以设置一个警报,在指定的秒数后触发您的代码:

    eval {
        local $SIG{ ALRM } = sub { die "TIMEOUT" };
        alarm 3 * 60;
        for (my $i = 0 ; $i <$QUOTA ; $i++) {
            build_dyna_file($i);
            comp_simu_exe;
            bin2txt2errormap($i);
        }
        alarm 0;
    };
    
    if ( $@ && $@ =~ m/TIMEOUT/ ) {
        warn "operation timed out";
    }
    else {
        # somebody else died
        alarm 0;
        die $@;
    }
    

    或者,如果您确实需要循环至少运行 3 次,无论这可能需要多长时间:

    eval {
        my $t0 = time;
        local $SIG{ ALRM } = sub { die "TIMEOUT" };
    
        for (my $i = 0 ; $i <$QUOTA ; $i++) {
            build_dyna_file($i);
            comp_simu_exe;
            bin2txt2errormap($i);
            if ( $i == 3 ) {
                my $time_remaining = 3 * 60 - time - $t0;
                alarm $time_remaining if $time_remaining > 0;
            }
        }
        alarm 0;
    };
    

    【讨论】:

    • 如果$@"TIMEOUT" 不匹配,请记住运行alarm 0;。代码可能会在块 eval 内死掉,而里面的 alarm 0; 将永远不会运行。这会导致带有消息“闹钟\n”的虚假dies。
    • 感谢您的快速答复。我的意思是运行一个脚本并在它运行超过 3 分钟时终止该进程。我尝试了您的建议,它实际上在 10 秒后停止(我尝试了 10 秒),但又过了 10 秒,它从断点继续。
    • Yohad:你是如何启动必须在 x 秒后完成的脚本的?
    • 所写?抱歉,我没听懂。
    • 最好使用范围保护来执行“警报 0”,这样代码就不会重复。 "use Guard; eval { my $guard = scope_guard { alarm 0 }; }" 现在,无论 eval 块如何退出,您总是会重置警报。
    【解决方案2】:

    这是处理第二个进程超时情况的第二个答案。使用此案例启动您的外部程序并确保它不会花费太长时间:

    my $timeout = 180;
    my $pid = fork;
    
    if ( defined $pid ) {
        if ( $pid ) {
            # this is the parent process
            local $SIG{ALRM} = sub { die "TIMEOUT" };
            alarm 180;
            # wait until child returns or timeout occurs
            eval {
                waitpid( $pid, 0 );
            };
            alarm 0;
    
            if ( $@ && $@ =~ m/TIMEOUT/ ) {
                # timeout, kill the child process
                kill 9, $pid;
            }
        }
        else {
            # this is the child process
            # this call will never return. Note the use of exec instead of system
            exec "simulator --shell";
        }
    }
    else {
        die "Could not fork.";
    }
    

    【讨论】:

    • 谢谢它似乎工作正常但是当我使用 EXEC 时它退出 PERL 脚本,我需要继续 for-loop 部分。我可以以某种方式使用系统调用而不是 exec 调用吗?
    • 如果你每次都在循环中进行分叉,你应该没问题。
    • 曼尼,非常感谢您的帮助!它完美地完成了工作!
    【解决方案3】:

    在 Perl 中处理此类超时的方式是 alarm 函数。在您传递给它的秒数后,它将向您的进程发送信号ALRM。如果您尝试为调用 sleep 设置超时的代码,请小心,因为它们在许多平台上不能很好地混合。基本结构如下所示:

    #start a block eval to stop the die below from ending the program
    eval {
        #set the signal handler for the ALRM signal to die if it is run
        #note, the local makes this signal handler local to this block
        #eval only
        local $SIG{ALRM} = sub { die "timeout\n" };
    
        alarm $wait; #wait $wait seconds and then send the ALRM signal
    
        #thing that could take a long time
    
        alarm 0; #turn off the alarm (don't send the signal)
    
        #any true value will do, if this line is reached the or do below won't run
        1; 
    } or do {
        #if we are in this block something bad happened, so we need to find out
        #what it was by looking at the $@ variable which holds the reason the
        #the code above died
        if ($@ eq "timeout\n") {
            #if $@ is the timeout message then we timed out, take the
            #proper clean up steps here
        } else {
            #we died for some other reason, possibly a syntax error or the code
            #issued its own die.  We should look at $@ more carefully and determine
            #the right course of action.  For the purposes of this example I will
            #assume that a message of "resource not available\n" is being sent by
            #the thing that takes a long time and it is safe to continue the program.
            #Any other message is unexpected.
    
            #since we didn't timeout, but the code died, alarm 0 was never called
            #so we need to call it now to prevent the default ALRM signal handler
            #from running when the timeout is up
            alarm 0;
    
            if ($@ eq "resource not available\n") {
                warn $@;
            } else {
                die $@;
            }
     }
    

    或者写得更紧凑:

    eval {
        local $SIG{ALRM} = sub { die "timeout\n" };
    
        alarm $wait; #wait $wait seconds and then send the ALRM signal
    
        #thing that could take a long time
    
        alarm 0;
    
        1;
    } or do {
        die $@ unless $@ eq "timeout\n" or $@ eq "resource not available\n";
        alarm 0;
        warn $@;
    }
    

    【讨论】:

    • 您的建议也会发生这种情况。 x 秒后,程序退出到终端 - 好的。但随后在终端的 x 秒后 - 程序无处可去,并从它停止的地方继续并结束其任务。你认为什么可能是死而复生的问题?
    猜你喜欢
    • 2011-09-03
    • 2010-09-09
    • 1970-01-01
    • 2012-01-23
    • 2016-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-25
    相关资源
    最近更新 更多