我该如何限制在Perl脚本中特定部分的执行时间?

7
有没有办法构建一个时间计数器,使得脚本的某些部分在它滴答滴答地运行时可以运行?例如,我有以下代码:
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");
}

系统功能调用与死机问题之间有没有任何联系?
3个回答

12
您可以设置一个闹钟,在指定的秒数后中断您的代码:

您可以设置一个闹钟,在指定的秒数后中断您的代码:

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 $@;
}

或者,如果您真的需要循环运行至少三次,无论这需要多长时间:

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

4
如果$@不匹配"TIMEOUT",请记得运行alarm 0;。如果代码在块eval中出现问题,并且内部的alarm 0;没有运行,那么就会出现带有消息 "Alarm clock\n" 的虚假die - Chas. Owens
谢谢您的快速回复。我的意思是运行一个脚本,如果它运行超过3分钟就终止该进程。我尝试了您的建议,它确实在10秒钟后停止(我尝试了10秒钟),但然后,在另外10秒钟后,它从断点处继续执行。 - YoDar
Yohad:你是如何启动需要在x秒后完成的脚本的? - innaM
1
我写的?抱歉,我不明白。 - innaM
4
最好使用作用域保护器来执行"alarm 0",这样就不会重复编写代码。使用以下代码:"use Guard; eval { my $guard = scope_guard { alarm 0 }; <code> }",现在无论 eval 块如何退出,都会始终重置警报。 - jrockway
显示剩余11条评论

5

这里是第二个答案,涉及到超时第二个进程的情况。使用此方法启动您的外部程序,并确保它不要花费太长时间:

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循环部分。我能否以某种方式使用系统调用代替exec调用? - YoDar
如果你在每次循环中都执行分叉操作,那么应该没问题。 - innaM
Manni,非常感谢你的帮助!它完美地完成了工作! - YoDar

4
使用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 秒后 - 程序从无处而来,继续从停止的地方继续执行并完成任务。你认为可能是什么问题导致了这个死亡问题? - YoDar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接