问题描述
我正在尝试在我的 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,'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
返回,将调用信号处理程序。
大多数阻塞系统调用都可以被中断(返回错误 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;