【问题标题】:Does join in perl threads block SIGALRM?加入 perl 线程会阻塞 SIGALRM 吗?
【发布时间】:2016-06-07 00:49:57
【问题描述】:

我有一个挂在 perl 5.16.3 上的小示例程序。我正在尝试使用alarm 来触发如果两个线程在一个更复杂的程序中没有及时完成工作,但这归结为它的要点。我知道还有很多其他方法可以做到这一点,但为了争论,假设我坚持使用代码。我不确定这是否是 perl 中的错误,或者某些合法不应该工作的东西。

我在互联网上对此进行了研究,似乎通常不鼓励混合警报和线程,但我看到很多例子,人们声称这是完全合理的做法,例如 this other SO question ,Perl threads with alarm。已接受的关于该问题的答案中提供的代码也挂在我的系统上,这就是为什么我想知道这是否是现在已经损坏的东西,至少从 5.16.3 开始。

在下面的代码中,如果我在alarm 关闭之前调用joinalarm 将永远不会触发。如果我将join 替换为while(1){} 并进入忙等待循环,那么alarm 会正常关闭,因此join 似乎出于某种原因阻塞了SIGALRM。

我的预期是join 发生,然后几秒钟后我看到“警报!”打印在屏幕上,但这永远不会发生,只要在 alarm 关闭之前调用 join

#!/usr/bin/env perl

use strict;
use warnings;
use threads;

sub worker {
    print "Worker thread started.\n";
    while(1){}
}

my $thread = threads->create(\&worker);

print "Setting alarm.\n";
$SIG{ALRM} = sub { print "Alarm!\n" };
alarm 2;

print "Joining.\n";
$thread->join();

【问题讨论】:

  • 我仍然不相信这是“完全合理的”,因为它们来自两种不同型号的 IPC。

标签: multithreading perl


【解决方案1】:

问题与线程无关。信号只有在Perl ops之间processed,而join是用C写的,所以只有join返回时才会处理信号。下面证明了这一点:

#!/usr/bin/env perl

use strict;
use warnings;
use threads;

sub worker {
    print "Worker thread started.\n";
    for (1..5) {
       sleep(1);
       print(".\n");
    }
}

my $thread = threads->create(\&worker);

print "Setting alarm.\n";
$SIG{ALRM} = sub { print "Alarm!\n" };
alarm 2;

print "Joining.\n";
$thread->join();

输出:

Setting alarm.
Joining.
Worker thread started.
.
.
.
.
.
Alarm!

join 本质上是对pthread_join 的调用。与其他阻塞系统调用不同,pthread_join 不会被信号中断。


顺便说一句,我将$tid 重命名为$thread,因为threads->create 返回的是线程对象,而不是线程ID。

【讨论】:

  • 这听起来完全合理,除了人们发布的其他示例,这些示例声称有效,例如其他 SO 问题。此外,查看 perldocs,他们也声称相反。用于警报的 perldoc 明确指出,“如果您想使用警报来使系统调用超时……”而 perlipc 的 perldoc 说:“信号处理也用于 Unix 中的超时……然后尝试您的阻塞操作。 ……”。为什么 join 与其他系统调用不同?
  • $thread->join 并不特殊,这与线程无关。我所说的适用于所有用 C 编写的函数。这甚至包括正则表达式匹配 ($s =~ /$re/)。现在,当信号进来时,一些阻塞的系统系统调用会提前返回错误EINTREAGAIN,但$thread->join 实际上是对mutex_join 的调用,而mutex_join 没有返回的方法一个错误。它会一直阻塞,直到成功为止。
  • 再一次,完全愿意相信这一点,但 C 中的相同代码不会以这种方式运行。我希望 perl 实现在底层使用相同的 C 库,或者这是一个错误的假设?还是 pthread 是以更理智的方式编写的?在 C 语言中,我可以执行 pthread_join,而我的 alarm 仍然按计划关闭。
  • 是的,但是在 Perl 中,信号仅在 Perl 操作之间进行处理。在运行其他 Perl 代码的过程中运行 Perl 代码是不安全的,因此实际的信号处理程序只是增加一个在 Perl 操作之间检查的计数器。 // 你说它在 C 中有所不同,但事实并非如此。编写良好的 C 程序中的信号处理程序很可能会做完全相同的事情。你不能在任意代码中间抛出异常。
  • 问题不在于闹钟何时响起。它在 Perl 中也“按计划进行”。问题是join/pthread_join什么时候返回。在 Perl 和 C 中也是如此。
【解决方案2】:

我将发布我自己的问题的答案,以在上面 ikegami 的回复中添加一些细节,并总结我们的对话,这将使未来的访问者不必阅读它收集的大量评论线索。

在与 ikegami 讨论之后,我又去阅读了一些关于 perl 信号的内容,咨询了其他一些 perl 专家,并发现了 join 没有被解释器“打断”的确切原因。正如 ikegami 所说,信号仅在 perl 操作之间传递。在 perl 中,这称为Deferred Signals, or Safe Signals

延迟信号于 2002 年在 5.8.0 中发布,这可能是我在网上看到旧帖子似乎不起作用的原因之一。他们可能使用“不安全的信号”,这更像是我们在 C 中习惯的信号传递。事实上,从 5.8.1 开始,您可以通过在执行之前设置环境变量 PERL_SIGNALS=unsafe 来关闭延迟信号传递你的脚本。当我这样做时,threads::join 调用确实像我预期的那样被中断,就像在同一场景中 pthread_join 在 C 中被中断一样。

与其他 I/O 操作不同,例如 read,它在信号中断时返回 EINTR,threads::join 不这样做。在后台,它是对 C 库调用 pthread_join 的调用,手册页确认它不会返回 EINTR。在延迟信号下,当解释器获得 SIGALRM 时,它会安排信号的传递,延迟它,直到threads::join->pthread_join 库调用返回。由于pthread_join 不会“中断”并返回 EINTR,因此我的 SIGALRM 被threads::join 有效地吞噬了。对于其他 I/O 操作,它们会“中断”并返回 EINTR,让 perl 解释器有机会传递信号,然后通过 SA_RESTART 重新启动系统调用。

显然,在不安全的信号模式下运行可能是一件坏事,因此作为替代方案,根据perlipc,您可以使用 POSIX 模块直接通过sigaction 安装信号处理程序。这会使一个特定的信号“不安全”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    相关资源
    最近更新 更多