Perl多线程中Alarm::Concurrent的setalarm未触发处理函数问题
为什么Alarm::Concurrent的setalarm没有触发超时逻辑?
这个问题我之前排查过类似的,核心原因是你对Alarm::Concurrent的回调执行环境理解有误:这个模块的回调函数是在它自己创建的独立闹钟线程里运行的,不是在你调用setalarm的工作线程中。
你在回调里写的die "Timedout\n"只会终止那个后台的闹钟线程,完全不会干扰到你的工作线程——工作线程里的sleep会老老实实跑完指定的5秒,然后执行clearalarm,自然走正常退出的分支,这就是为什么所有线程都输出了EXITED。
怎么验证这个?
你可以给回调加一行打印线程ID的代码,一眼就能看明白:
setalarm 3, sub { print "Alarm thread TID: " . threads->tid() . "\n"; die "Timedout\n"; };
运行后会发现,输出的闹钟线程ID和你的工作线程ID完全不一样,说明这俩根本不是同一个线程,工作线程的eval当然捕获不到另一个线程里抛出的异常。
修复方案:让闹钟线程给工作线程发信号
要让超时逻辑真正作用于工作线程,我们需要让闹钟线程向工作线程发送信号,触发工作线程内部的异常。修改后的代码如下:
use threads ('yield', 'stack_size' => 64*4096, 'exit' => 'threads_only', 'stringify', 'signal'); use Alarm::Concurrent qw(setalarm clearalarm); threads->create(\&MyThreadFunc,1); threads->create(\&MyThreadFunc,1); threads->create(\&MyThreadFunc,5); threads->create(\&MyThreadFunc,5); sub MyThreadFunc { my $result = "Never Ran"; my $timeToSleep = $_[0]; my $tid = threads->tid(); my $self_thread = threads->self(); # 给当前工作线程注册SIGALRM信号处理:收到信号就抛超时异常 $SIG{ALRM} = sub { die "Timedout\n" }; eval { # 闹钟触发时,向当前工作线程发SIGALRM信号 setalarm 3, sub { $self_thread->signal('ALRM') }; sleep ($timeToSleep); clearalarm; # 正常执行完就取消闹钟 $result = "TID $tid EXITED\n"; }; # 恢复默认的信号处理,避免影响其他逻辑 $SIG{ALRM} = 'DEFAULT'; if ($@ eq "Timedout\n") { $result = "TID $tid TIMED OUT\n"; } elsif ($@) { $result = "$@"; } print "Thread returning $result"; return $result; } while (scalar threads::list(threads::running)) { foreach my $thread (threads->list(threads::joinable)) { $thread->join(); } }
修复逻辑拆解
- 先在工作线程里注册
SIGALRM信号处理函数,收到信号就抛出Timedout异常,让eval能捕获到。 - 把
setalarm的回调改成向当前工作线程发送SIGALRM信号,而不是直接die——这样信号会投递到目标工作线程。 - Perl的
sleep函数会被信号打断,收到SIGALRM后立刻退出睡眠,触发信号处理函数抛出异常,被eval捕获后进入超时分支。 - 最后恢复默认的信号处理,避免对后续代码造成意外影响。
这样修改后,就能实现你预期的效果:TID1、2正常退出,TID3、4触发超时。
内容的提问来源于stack exchange,提问作者BarkerChippy




