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.9.1   Reconstruya el programa explicado en esta sección y ejecutélo con la ayuda del depurador

Casiano Rodríguez León
2010-03-22
$child->{return_value} ) { 41 kill( 9, $pid ); 42 $child->{return_value} = -1; 43 } 44 } 45 } 46 } 47 } while ( $child > 0 ); Si la opción abort_on_error está activada y un proceso termina con un código de error se eliminan todos los procesos arrancados y que no hayan finalizado. Para ello se comprueba el estatus de retorno del proceso almacenado en $?. Si es distinto de cero se procede a abortar los procesos hijo envíandoles mediante la llamada kill(9, $pid ).

La función kill permite el envío de señales a otros procesos. Su modo de uso es:

$count = kill($signal, @processes);

La llamada envía la señal $signal a los procesos cuyos PID están en la lista @processes. El resultado devuelve el número de procesos a los que la señal llegó con éxito. Las señales serán estudiadas en mas detalle en la sección 3.4.

Nótese que pudiera ocurrir que un proceso p1 terminará con éxito al mismo tiempo o poco después que otro proceso p2 lo hace con error. En tal caso la llamada a kill de la línea 41 sobre p1 fracasa en matar el zombi p1 ya que este ''ha muerto''. Sin embargo se establece la entrada $child->{return_value} a -1. En este caso el waitpid de la línea 33 cosecha posteriormente al zombi resultante de p1. Observe que bajo estas circunstancias la condición de la línea 36 'defined $child_registry{$child}{return_value}' es cierta.

Por último se calcula el valor de retorno. La variable $return_values contendrá un hash anónimo con las parejas formadas por el nombre lógico del proceso y su valor de retorno si la llamada a prun usó el formato con nombres. En otro caso $return_values contiene una referencia a un array con los valores de retorno. Para ordenar los valores de retorno (líneas 52 y 53) es necesario usar el criterio de comparación numérica ($a->{name} <=> $b->{name}).

49     # store return values using appropriate data type
50     $return_values = $named
51         ? { map { $_->{name} => $_->{return_value} } values %child_registry }
52         : [ map { $_->{return_value} } sort { $a->{name} <=> $b->{name} } 
53                 values %child_registry 
54           ];
55 
56     my $num_blocks = keys %blocks;
57     return 1 if ( $successes == $num_blocks );  # all good!
58 
59     $error = "only $successes of $num_blocks blocks completed successfully";
60     return 0;  # sorry... better luck next time
61 }
El número de claves en %blocks es el número total de procesos.

Ejercicio 3.8.1   Lea en avance la sección 3.3. Use el debugger de perl para estudiar la conducta de un programa con forks de manera que le ayude a comprender el código de prun.

Casiano Rodríguez León
2010-04-19
0">Contrucción de una URI a partir de una Cadena
  • El Método Clone
  • Los Setters de los Objetos URI
  • URIs en Contexto de Cadenas
  • Normalización de URIs
  • Comparación de URIs
  • Métodos Auxiliares
  • Ausencia de Ciertas Componentes
  • Argumentos de una LLamada
  • Conversión de una URL Relativa en una Absoluta
  • Conversión de una URL Absoluta en una Relativa
    Casiano Rodríguez León
    2010-04-20