1

所以我最近想线程化我的一个 Perl 程序来提高它的速度。获取网站列表,我想为每个 url 启动一个线程并获取每个网站的内容,然后在页面上查找公司描述。一旦一个线程找到结果,或者所有线程都没有,我想退出,写下我的结果,然后读入我下一家公司的网址。

我看到的问题是我在创建线程时调用的函数内部使用了 Perl::Unsafe::Signals 模块。我需要不安全的信号来中断“卡住”的正则表达式。然而,这似乎会导致各种问题,主要是程序崩溃和错误消息“闹钟”显示。

因此,有没有办法安全地使用 Perl::Unsafe::Signals 和线程?有没有办法通过向函数发送信号来以另一种方式使正则表达式超时(就像我在下面发送“KILL”信号?)谢谢。

注意:我将代码剥离到所有相关部分,如果您需要更多,请告诉我。

use threads ('exit' => 'threads_only');
use threads::shared;
my @descrip;
share(@descrip);

my $lock;
share($lock);

URL:foreach my $url(@unique_urls) {
        #skip blank urls
        if(!$url) { next URL; }#if

        #find description
        my $thread = threads->create(\&findCompanyDescription, $PREV_COMPANY, $PREV_BASE_URL, $url);

#while a description has not been found and there are still active threads, keep looking
#there may be a better way to do this, but this seems to work for me
while(!@descrip && threads->list() != 0) {;}

#kill all threads, write output, read in next batch of urls
my @threads = threads->list();
foreach(@threads) { print("detaching\n"); $_->kill('KILL')->detach(); }#foreach
#######线程创建调用的子程序
sub findCompanyDescription {
    my($company_full, $base_url, $url) = @_;
    my($descrip, $raw_meta, $raw) = '';
    my @company;

    $SIG{'KILL'} = sub { alarm(0); threads->exit(); };

    eval {
        local $SIG{ALRM} = sub { die("alarm\n") }; # NB: \n required
        alarm(5);

        use Perl::Unsafe::Signals;
        UNSAFE_SIGNALS {

            while($company) {
            my @matches = ($content =~ m!.*<([\w\d]+).*?>\s*about\s+$company[\w\s\-_]*<.*?>(?:<.*?>|\s)*(.*?)</\1.*?>!sig);

            MATCH:for(my $ndx=1; $ndx<@matches; $ndx+=2) {
            ($raw, $descrip) = &filterResult($matches[$ndx], $company_full);

            if($descrip) {
                $company = undef;
                last(MATCH);
            }#if
        }#for

        #reduce the company name and try again
        $company = &reduceCompanyName($company);

        }#while

        alarm(0);
         };#unsafe_signals
    };#eval 

    if($@) {
        if($@ eq "alarm\n" && $DEBUG) { print("\nWebpage Timeout [].\n"); }#if
    }#if

    if($descrip) { lock($lock); {
       @descrip = ($PREV_ID, $company_full, $base_url, $url, 1, $raw, $descrip); } 
    }#if
4

3 回答 3

7

一般来说,“不安全”信号对于单线程和多线程都是不安全的。使用线程不安全信号只会增加风险。Perl 的常用安全信号处理程序设置标志signal_pending而不会有意义地中断执行。VM 在操作码之间检查该标志。

您的正则表达式执行是一个单一的“原子”操作码。当然,正则表达式本身是另一个具有自己操作码的 VM,但我们目前无法看到 perl 信号处理程序。

坦率地说,我不知道如何中断正则表达式引擎。它有一些全局 C 状态,过去在 perl-5.10 之前阻止它重入。像您正在尝试的通用可中断性可能不安全。如果您真的希望它完全可中断,您可能希望分叉并让您的子进程执行正则表达式并通过管道将结果传回。

require JSON;
require IO::Select;

my $TIMEOUT_SECONDS = 2.5; # seconds

my ( $read, $write );
pipe $read, $write;

my @matches;
my $pid = fork;
if ( $pid ) {

    my $select = IO::Select->new( $read );
    if ( $select->can_read( $TIMEOUT_SECONDS ) ) {
        local $/;
        my $json = <$read>;
        if ( $json ) {
            my $matches_ref = JSON::from_json( $json );
            if ( $matches_ref ) {
                @matches = @$matches_ref;
            }
        }
    }
    waitpid $pid, 0;
}
else {
    my @r = $content =~ m!.*<([\w\d]+).*?>\s*about\s+$company[\w\s\-_]*<.*?>(?:<.*?>|\s)*(.*?)</\1.*?>!sig;
    my $json = JSON::to_json( \ @r );
    print { $write } $json;
    close $write;
    exit;
}
于 2010-08-30T20:53:24.297 回答
2

恕我直言,混合信号和线程本身就是一项具有挑战性的任务(即没有特定于 perl 的东西)。请记住,即使在单线程程序中,您也只能从信号处理程序安全地调用异步信号安全函数,因为程序可能随时被中断。Perl 添加了另一层抽象,所以我不知道在不安全信号的情况下从信号处理程序调用“die”的安全性。

如果我没记错的话,SIGALRM 是异步信号,所以必须同步处理。在多线程程序中,您处理它的方式通常是不正确的。

此外,恕我直言,perl 线程并不像大多数人期望的那样工作。只需避免使用它们,而是使用流程。

附言

以下行没有意义:

$SIG{'KILL'} = sub { alarm(0); threads->exit(); };

无法捕获 SIGKILL(以及 SIGSTOP)。

于 2010-08-30T21:31:35.237 回答
1

我不是 Perl-MT 方面的专家,但您显然缺少的一件事是信号对于整个过程是全局的——它们不是特定于线程的。在 POSIX 系统上,您不能为线程设置信号处理程序:信号被传递到整个进程。IOWalarm()调用会影响整个进程,而不仅仅是调用它的线程。即使local %SIG在 MT 上下文中,它也不会像人们认为的那样做——因为它local是一种语法。

于 2010-08-31T12:18:19.127 回答