Все задачи

Скачать все, параллельно

03 Mar 2014

Программист решил усовершенствовать скрипт для скачивания веб-страниц из задачи “Скачать все”, и добавить в него параллельности с помощью модуля Parallel::ForkManager.

Итак, программист взял пример из документации, добавил логгирование, получилось примерно так:

#!/usr/bin/perl

use strict;
use warnings;

use Log::Log4perl;
use LWP::Simple;
use Parallel::ForkManager;

# links to download
my @links = (
    ["http://www.cpan.org/misc/images/cpan.png", "cpan-logo.png"],
    ["https://metacpan.org/static/images/logo.png", "metacpan-logo.png"],
    ["http://yandex.st/www/1.817/yaru/i/logo.png", "yandex-logo.png"],
    ["https://www.google.ru/images/srpr/logo11w.png", "google-logo.png"],
    ["https://mmedia2.ozone.ru/graphics/ozon/logo_220_87.png", "ozon-logo.png"],
    ["http://www.foo.bar/rulez.data","rulez_data.txt"],
    ["http://new.host/more_data.doc","more_data.doc"],
    # ...
);
my $chunk_size = 2;

my $conf = qq(
log4perl.rootLogger                = DEBUG, SYSLOG
log4perl.appender.SYSLOG           = Log::Dispatch::Syslog
log4perl.appender.SYSLOG.min_level = debug
log4perl.appender.SYSLOG.ident     = $0 [$$]
log4perl.appender.SYSLOG.facility  = daemon
log4perl.appender.SYSLOG.layout    = Log::Log4perl::Layout::SimpleLayout
);

Log::Log4perl::init( \$conf );
my $logger = Log::Log4perl->get_logger();

# Max 30 processes for parallel download
my $pm = Parallel::ForkManager->new(30);

while ( my @chunk = splice @links, 0, $chunk_size) {
    $pm->start and next; 

    $logger->info((scalar @chunk)." links to download");

    for my $linkarray (@chunk){
        my ($link,$fn) = @$linkarray;
        my $status = getstore($link,$fn);
        $logger->warn("Cannot get $fn from $link") if $status != RC_OK;
    }

    $pm->finish; 
}

$pm->wait_all_children;
$logger->info("done");

Скрипт работал, но странновато…

Подсказка

Показать

Судя по логу, все неудачные скачивания приходились на один и тот же процесс-воркер. Странное совпадение.

Подсказка-2

Показать

Впрочем, судя по логу, вообще вся обработка ссылок происходила в одном и том же процессе. А как же параллельность, форки, воркеры?

Разоблачение

Показать

Проблема в том, что логгер инициализируется в родительском процессе, и в момент инициализации вычисляется строка-идентификатор с именем и номером текущего, то есть родительского процесса. Дочерние процессы получают готовый объект-логгер, и их записи в логе все равно помечены родительским pid-ом.

Решением было бы инициализировать логгер в каждом процессе отдельно.