Перл Multi-Threaded программных сбоев спорадически

Я написал программу в Perl, которая использует многопоточность. Я использую эту программу, чтобы понять, как многопоточность реализована в Perl.

Сначала краткий обзор того, что программа намеревается сделать: он будет читать список URL-адресов из текстового файла, по одному за раз. Для каждого URL-адреса он вызывает подпрограмму (передавая URL-адрес в качестве параметра) и отправляя ему запрос HTTP HEAD. После получения заголовков HTTP-ответа он выведет поле заголовка сервера из ответа.

Для каждого URL-адреса он запускает новый поток, который вызывает вышеупомянутую подпрограмму.

Проблема: Основная проблема заключается в том, что программа периодически прерывается. Он работает нормально в другое время. Это, по-видимому, ненадежный код, и я уверен, что есть способ заставить его работать надежно.

Код:

#!/usr/bin/perl

use strict;
use warnings;
use threads;
use WWW::Mechanize;
no warnings 'uninitialized';

open(INPUT,'<','urls.txt') || die("Couldn't open the file in read mode\n");

print "Starting main program\n";

my @threads;

while(my $url = <INPUT>)
{
    chomp $url;
    my $t = threads->new(\&sub1, $url);
    push(@threads,$t);
}

foreach (@threads) {
    $_->join;
}

print "End of main program\n";

sub sub1 {
    my $site = shift;
    sleep 1;
    my $mech = WWW::Mechanize->new();
    $mech->agent_alias('Windows IE 6');

    # trap any error which occurs while sending an HTTP HEAD request to the site
    eval{$mech->head($site);};
    if([email protected])
    {
        print "Error connecting to: ".$site."\n";
    }

    my $response = $mech->response();

    print $site." => ".$response->header('Server'),"\n";
}

Вопросы:

Как я могу заставить эту программу работать надежно и в чем причина спорадических сбоев?

Какова цель вызова метода объединения объекта потока?

В соответствии с документацией по приведенной ниже ссылке она будет ждать завершения выполнения потока. Я вызываю метод соединения правильно?

http://perldoc.perl.org/threads.html

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

Мне нужно вызвать sleep() исключительно в коде или не требуется?

В C мы вызываем Sleep() после вызова CreateThread(), чтобы начать выполнение потока.

Что касается сбоя: когда вышеприведенный код Perl неожиданно и спорадически возникает, появляется сообщение об ошибке: "Perl-интерпретатор командной строки перестает работать"

Подробности сбоя:

Fault Module Name:  ntdll.dll
Exception Code: c0000008

Вышеуказанный код исключения соответствует: STATUS_INVALID_HANDLE

Возможно, это соответствует недопустимому дескриптору потока.

Информация о моей установке на Perl:

Summary of my perl5 (revision 5 version 14 subversion 2) configuration:

Platform:
osname=MSWin32, osvers=5.2, archname=MSWin32-x86-multi-thread
useithreads=define

Подробная информация о ОС: Win 7 Ultimate, 64-разрядная ОС.

Надеюсь, что этой информации будет достаточно, чтобы найти основную причину проблемы и исправить код.

Ответ 1

В коде нет ничего плохого. Возможно, ваши ожидания слишком высоки.

Перловые потоки реализованы путем создания нескольких экземпляров интерпретатора в рамках одного и того же процесса операционной системы. Это изолирует код Perl в каждом потоке от всех остальных (он ничего не имеет). То, что он не делает (и не может), это изолировать код, который не находится под контролем perl. То есть, любой модуль с компонентом, написанным на C. Например, быстрый просмотр WWW:: Mechanize показывает, что он имеет возможность использовать zlib для сжатия, если он установлен. Если это используется, и что код C не является достаточно потокобезопасным, это может быть, возможно, проблема с сбоем. Поэтому, если вы хотите быть уверенным, что ваше приложение Perl будет хорошо работать под потоками, вам необходимо пройти через все модули, которые он использует (и все модули, которые они используют), и проверить, что они либо не имеют частей без Perl, либо что эти части потокобезопасный. Для большинства нетривиальных программ это необоснованное количество работы (или необоснованное ограничение на то, какие модули CPAN вы можете использовать).

Скорее всего, это значительная часть причины, по которой потоки не используются в Perl.

Ответ 2

Я использовал многопоточность в perl для создания больших систем. Раздел, в котором вы начинаете темы и дожидаетесь их окончания, выглядит хорошо для меня.

Чтобы ответить на ваши вопросы:

  • Сон не требуется.

  • То, как вы вызываете соединение, является правильным, оно будет блокироваться до тех пор, пока все потоки не закончатся.

Я бы сделал следующее:

  • Попробуйте прокомментировать код механизации. Просто чтобы убедиться, что это не тот, кто вызывает это. Вместо этого у вас есть случайный сон. Посмотрите, продолжает ли ваш script.

  • Попробуйте удалить многопоточность и посмотреть, вызывает ли вызов функцию несколько раз (имеет цикл for или что-то еще) вызывает какие-либо проблемы.

Ответ 3

Одна маленькая вещь "лучших практик", которая выскочила на меня, заключалась в том, что вы используете три параметра open (good), но дескриптор дескриптора (boo!). Я всегда стараюсь использовать "и" и "или" вместо "& &". и "или" тоже. Они являются операторами с наименьшим приоритетом, поэтому (для меня, по крайней мере) проще всего использовать правильные команды разделения. Я склонен использовать && и || только внутри тернарного оператора или с правой стороны равных, например my $a = func() || 'по умолчанию';

Итак, чтобы открытая строка я писал:

open my $input, '<', 'urls.txt; or die "Couldn't open `urls.txt' for read: $!";

Ответ 4

Вместо этого я рекомендую использовать подход многоразовых потоков. См. Этот пример: Повторная съемка тем

Также проверьте отличный модуль Thread:: Queue:

use threads;
use Thread::Queue;

my $q  = Thread::Queue->new();
my $pq = Thread::Queue->new();

my $config = { number_of_threads => 10 };
my @threads = map { threads->create( \&worker, $q, $pq ) }
  ( 1 .. $config->{number_of_threads} );
push @threads, threads->create( \&controller, $q, $pq );

my @urls = read_urls($filename);

foreach my $url (@urls) {

    process_url( $q, $url );
}

while ( my $pend = $q->pending() ) {

    sleep 1;
}

$q->enqueue(undef) for @threads;

while ( my $pend = $pq->pending() ) {

    sleep 1;
}

$pq->enqueue(undef);

foreach my $thr (@threads) {

    $thr->join();
}

sub worker {
    my ( $q, $pq ) = @_;
    while ( my $url = $q->dequeue() ) {

        my $result = check_url($url);
        $pq->enqueue($result);
    }

    printf "Finishing tid(%s)\n", threads->tid;
    return;
}

sub controller {
    my ( $q, $pq ) = @_;
    while ( my $result = $pq->dequeue() ) {

        save_result($result);
    }

    printf "Finishing Controller tid(%s)\n", threads->tid;
    return;
}

sub process_url {
    my ( $q, $url ) = @_;

    $q->enqueue($url);
    return;
}