如果我设置 $SIG{ALRM},警报似乎不会触发

alarm does not seem to fire if I set $SIG{ALRM}

我正在尝试在我的 Perl 后端进程中实现一个警报,以便它在卡住时间过长时终止。我试图实现 alarm documentation page on Perldoc 上给出的代码(这是文档中的逐字记录,而不是调用我程序的关键子例程的行而不是文档中的示例行):

eval {
    local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm $timeout;
    &FaithTree::Backend::commandLine({ 'skipWidgets' => $skipWidgets, 'commandLineId' => $commandLineId, 'force' => $force });
    alarm 0;
};
if ($@) {
    die unless $@ eq "alarm\n";   # propagate unexpected errors
    # timed out
}
else {
    # didn't
}

鉴于此代码,当警报应该超时时什么也没有发生。另一方面,如果我删除 $SIG{ALRM} 的自定义定义(再次直接来自 Perl 文档),警报会触发,只是没有自定义处理程序。

我想知道我正在使用 Thread::Queue 这一事实是否在警报失败中发挥了作用,但这并不能解释为什么只要我跳过重新定义 $SIG{ALRM}.

这是一个最小的、可运行的版本,其中的子例程是故意为测试设置的无限循环:

eval {
    $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
    alarm 1;
    &FaithTree::Test::Backend::commandLine({ 'skipWidgets' => $skipWidgets, 'commandLineId' => $commandLineId, 'force' => $force });
    alarm 0;
};


if ($@) {
    die unless $@ eq "alarm\n";   # propagate unexpected errors
    # timed out
}
else {
   exit;
}


package FaithTree::Test::Backend;

use File::Tail;
use threads;
use threads::shared;
use Thread::Queue;

sub commandLine {

    our $N //= 4;
    my $Q = new Thread::Queue;
    my @kids = map threads->create( \&FaithTree::Test::Backend::fetchChild, $Q ), 1 .. $N;  

    my @feeds = ( "1","2","3","4" );

    foreach my $feed (@feeds) {
        $Q->enqueue( $feed );
    }

    $Q->enqueue( ( undef ) x $N );
    $_->join for @kids; 

}

sub fetchChild {

    print "Test";
    # Access queue.
    my $Q = shift;

    #What is my thread id?
    my $tid = threads->tid();

    my ($num, $num2);

    for ( ; ; ){ 
        if ($num2 == 10000) {
            say STDERR $tid . ': ' . $num;
            $num2 = 0;
        }
    
        $num++; 
        $num2++;
    }

    return 1;

}

如果注释掉$SIG{ALRM}行,当闹钟设置超时时,它会终止。如果你把它留在原地,它永远不会终止。

信号和线程不能很好地混合。您可能需要重新考虑对信号的使用。例如,您可以将所有线程内容移动到子进程。


信号处理程序仅在 Perl 操作之间调用。主线程正在调用XS sub thread->join,信号处理函数会被调用一次join returns.

大多数阻塞系统调用都可以被中断(返回错误 EINTR),因此可以编写 join 的信号感知版本。除了我似乎记得 pthread 函数是不可中断的,所以也许不是。


在这种特殊情况下,您可以让线程在结束时向主线程发出信号,使用允许主线程阻塞直到信号发生或超时发生的系统。 cond_signal/cond_timedwait就是这样的系统。

use Sub::ScopeFinalizer qw( scope_finalizer );
use Time::HiRes         qw( time );

my $lock :shared;
my $threads_remaining = $N;
my $Q = new Thread::Queue;

my @threads;
{
   lock $lock;
   for (1..$N) {
      ++$threads_remaining;

      push @threads, async {
         my $guard = scope_finalizer {
            lock $lock;
            --$threads_remaining;
            cond_signal($lock);
         };

         worker($Q);
      }
   }
}

my $max_end_time = time + 1;

# ...

{
   lock $lock;
   while ($threads_remaining) {
      if (!cond_timedwait($lock, $max_end_time)) {
         # ... Handle timeout ...
      }
   }
}

$_->join for @threads;