Perl-потоки и небезопасные сигналы

Поэтому недавно я захотел использовать одну из моих программ на Perl, чтобы увеличить ее скорость. Взяв список веб-сайтов, я хотел создать ветку для каждого URL-адреса и получить содержимое каждого веб-сайта, а затем поискать описание компании на странице. Как только один поток нашел результат, или все потоки не нашли, я захотел выйти, написать свой результат и прочитать в 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
####### СУБРОУТИН, ВЫЗВАННЫЙ THREAD CREATE
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

3 ответа

Решение

Как правило, "небезопасные" сигналы небезопасны как для однопоточных, так и для многопоточных. Вы только увеличили свою опасность, используя потоки и небезопасные сигналы. Обычный безопасный обработчик сигнала в Perl устанавливает флаг signal_pending без значительного прерывания исполнения. ВМ проверяет этот флаг, когда он находится между кодами операций.

Ваше выполнение регулярного выражения представляет собой один "атомарный" код операции. Конечно, само регулярное выражение является еще одной виртуальной машиной со своими собственными кодами операций, но в настоящее время мы не видим этого для обработчика сигналов 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;
}

ИМХО, смешивание сигналов и потоков само по себе является сложной задачей (т.е. без специфических для perl вещей). Помните, что даже в однопоточной программе вы можете безопасно вызывать только асинхронно безопасные для сигнала функции из обработчика сигнала, потому что программа может быть прервана в любой момент. Perl добавляет еще один уровень абстракции, поэтому я понятия не имею о безопасности вызова "die" из обработчика сигналов в случае небезопасных сигналов.

Если я правильно помню, SIGALRM - это асинхронный сигнал, поэтому он должен обрабатываться синхронно. Ваш способ обращения с ним, как правило, неверен в многопоточных программах.

Более того, IMHO Perl темы просто не работают, как большинство людей ожидают. Просто избегайте их использования и используйте процессы вместо этого.

PS

Следующая строка не имеет смысла:

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

SIGKILL (как и SIGSTOP) не может быть пойман.

Я не очень специалист по Perl-MT, но одна вещь, которую вы явно упускаете, это то, что сигналы являются глобальными для всего процесса - они не являются специфичными для потоков. В системах POSIX вы не можете установить обработчик сигнала для потока: сигналы доставляются всему процессу. IOW alarm() вызов влияет на весь процесс, а не только на поток, который его вызывает. И даже local %SIG в контексте MT не делает то, что можно было бы подумать - потому что local это вещь синтаксиса.

Другие вопросы по тегам