El Módulo Parallel::ForkManager

El módulo Parallel::ForkManager provee una API OOP a la creación de procesos mediante fork. La funcionalidad adicional que se obtiene con el módulo es la posibilidad de proveer una cota superior al número de procesos que se pueden crear:

$pm = Parallel::ForkManager->new(5)

Una vez el objeto es creado es posible crear nuevos procesos hijo mediante el método start:

for my $link (@links) {
  $pm->start and next;

  get_link($link);
  $pm->finish;
};
start devuelve el pid del hijo al proceso padre y cero al hijo. La diferencia con un fork nativo es que start bloquea al proceso padre si la cota superior establecida en la llamada a new es sobrepasada.

La llamada al método finish termina el proceso hijo. Acepta un código opcional de salida. El código de salida por defecto es cero.

La llamada por parte del padre al método wait_all_children (línea 30 del código que sigue) hace que el padre no continúe hasta que todos los procesos hijo hayan terminado.

El código que sigue obtiene el nombre de un fichero HTML desde la línea de comandos. La llamada HTML::TreeBuilder->new_from_file($rootdoc) deja en $tree el árbol de análisis sintáctico del HTML. La llamada $tree->find_by_tag_name('a') retorna todos los nodos ''ancla'' existentes en el árbol (<a href"...">...</a>). El map de la línea 14 obtiene los atributos href de esos nodos. Mediante grep seleccionamos aquellas referencias a URL que sean completas.

El código de las líneas 23-30 se encarga de lanzar procesos concurrentes que descargan esos ficheros.

pp2@nereida:~/src/perl/forkmanager$ cat -n parforkaddr.pl
 1  #!/usr/local/bin/perl -w
 2  use strict;
 3  use Carp;
 4  use Parallel::ForkManager;
 5  use LWP::Simple;
 6  use HTML::TreeBuilder;
El módulo LWP::simple provee facilidades para la creación y manejo de objetos que se comportan como navegadores o browsers.

El módulo HTML::TreeBuilder proporciona failidades para el análisis sintáctico de fuentes HTML.

 8  my $debug = 1;
 9
10  my $rootdoc = shift || die "Usage $0 html_file_name\n";
11
12  my $tree = HTML::TreeBuilder->new_from_file($rootdoc);
Suponemos que el guión se llama con un fichero file.html. Los enlaces de la forma <a href="...">...</a> serán descargados por el guión en nuestra máquina local. La llamada a HTML::TreeBuilder->new_from_file crea el árbol de análisis sintáctico para el documento.
14  my @links = $tree->find_by_tag_name('a');
15  @links = map { $_->attr("href") } @links;
16  @links = grep { defined and m{http://.*\.html?$} } @links;
17  {
18    local $" = "\n\t";
19    print "Cargando:\n\t@links\n" if ($debug);
20  }
La llamada a find_by_tag_name devuelve una lista con todos los nodos ancla en el árbol. La transformación mediante map calcula los atributos href de esos nodos.

22  my $pm = Parallel::ForkManager->new(5);
23  for my $link (@links) {
24    $pm->start and next;
25
26    get_link($link);
27    $pm->finish;
28  };
29
30  $pm->wait_all_children;
31
32  sub get_link {
33    my $rootdoc = shift || croak "get_link error: provide a link\n";
34
35    my ($fn)= $rootdoc =~ /^.*\/(.*?)$/;
36    warn "Cannot determine filename from $fn\n", return 1 unless $fn;
37    my $rc=getstore($rootdoc,$fn);
38    my $result = is_success($rc);
La función getstore obtiene el documento y lo almacena. El resultado de la petición queda en $rc. La función is_success nos dice si la petición tuvo éxito.
39    if ($debug) {
40      my $prefix = $result? "$rootdoc downloaded": "Can't download $rootdoc";
41      print "$prefix. Response code: $rc\n";
42    }
43    return $result;
44  }

Ejercicio 3.10.1   Reconstruya el programa explicado en esta sección y ejecutélo con la ayuda del depurador

Casiano Rodríguez León
2012-02-29