Un Ejemplo con Threads

Ejemplo

En el ejemplo que sigue se muestran dos threads o hilos que comparten un recurso que debe ser accedido en exclusión mutua: la variable $locked la cuál ambas modifican. El ejemplo sirve para ilustrar la ventaja de usar una clausura: La variable $locked esta clausurada y compartida por las subrutinas tutu y titi:

lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ cat -n shared3.pl 
 1  #!/usr/bin/perl -w
 2  use strict;
 3  use threads;
 4  use threads::shared;
 5  use Time::HiRes(qw(usleep));
 6  use constant LIMIT => 10;
 7  {
 8    my $locked : shared = 0;
 9
10    sub tutu { 
11      my $self = threads->self(); # referencia al objeto thread
12      my $tid = $self->tid();
13      my $out = '';
14      for(1..LIMIT) {
15        usleep(int rand(2)); # dormir 2 microsegs
16        {
17          lock($locked); 
18          $locked += 1; 
19          $out .= "$tid:$locked ";
20        }
21      }
22      return $out;
23    }
24
25    sub titi { 
26      my $self = threads->self(); 
27      my $tid = $self->tid();
28      my $out = '';
29      for(1..LIMIT) {
30        usleep(int rand(2));
31        {  
32          lock($locked);
33          $locked += 2;
34          $out .= "$tid:$locked ";
35        }
36      }
37      return $out;
38    }
39  }
40
41  my $t = threads->new(\&tutu); # creamos la thread
42  my $rm = titi(); 
43  my $rs = $t->join(); # sincronizamos las dos threads
44  print "\nMaestro: $rm\nEsclavo: $rs\n";

Creación de un Hilo

El paquete threads, usado en la lınea 3, nos proporciona las herramientas para la creación de threads. El método new() (lınea 41) toma una referencia a una subrutina y crea una nueva thread que ejecuta concurrentemente la subrutina referenciada. Retorna una referencia al objeto que describe la thread. La llamada my $t = threads->new(\&tutu) es un ejemplo de llamada a un método de una clase. Se escribe el nombre del paquete/clase una flecha y el nombre del método.

Paso de Parámetros a la Tarea

Si se hubiera necesitado, es posible pasar parámetros a la subrutina como parte de la fase de arranque:

    $thr = threads->new(\&tutu, "Param 1", "Param 2", 4);

La llamada my $tid = $self->tid() devuelve el identificador de la thread. Este es un ejemplo de llamada a un método de un objeto. Se escribe el objeto seguido de una flecha y del nombre del método.

Sincronización con join

El método join() usado en la lınea 43 retorna cuando la thread $t termina. Además recolecta y retorna los valores que la thread haya retornado. En general es una lista:

@ReturnData = $t->join;

Cuando se crea un nuevo hilo, todos los datos asociados con el hilo actual se copian en el nuevo. Por tanto, las variables son, por defecto privadas a la thread. En la mayorıa de los casos se pretende que exista alguna forma de comunicación entre los hilos, para lo cual es conveniente disponer de mecanismos para hacer que ciertas variables sean compartidas por los hilos. Esta es la función del módulo threads::shared y del atributo shared (lınea 8).

Los Atributos

La idea de los atributos se introdujo en Perl en la versión 5.005. El programador puede definir atributos mediante el módulo http://search.cpan.org/~abergman/Attribute-Handlers/ el cual permite definir subrutinas que son llamadas cada vez que ocurre una aparición del atributo.

Por ejemplo, para crear un manejador se define una subrutina con el nombre del atributo:

  package LoudDecl;
  use Attribute::Handlers;

  sub Loud :ATTR {
          my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
          print STDERR
                  ref($referent), " ",
                  *{$symbol}{NAME}, " ",
                  "($referent) ", "was just declared ",
                  "and ascribed the ${attr} attribute ",
                  "with data ($data)\n",
                  "in phase $phase\n";
  }

Ahora cualquier aparición de una subrutina declarada con atributo :Loud en una clase que herede de LoudDecl:

  package LoudDecl;

  sub foo: Loud {...}

hace que el manejador sea llamado con argumentos:

  1. El nombre del paquete
  2. Una referencia a la entrada en la tabla de símbolos (typeglob)
  3. Una referencia a la subrutina
  4. El nombre de la subrutina
  5. Cualesquiera datos asociados con el atributo
  6. El nombre de la fase en la que es llamado

De la misma manera una declaración de una variable con el atributo :Loud

        package LoudDecl;

        my $foo :Loud;
        my @foo :Loud;
        my %foo :Loud;
Hace que el manejador sea llamado con una lista similar. Excepto que $_[2] es una referencia a una variable.

La función lock

La función lock proporcionada por el módulo threads::shared nos permite sincronizar el acceso a la variable. El cerrojo se libera al salir del contexto léxico en el que se produjo el lock. En el ejemplo, se libera al salir de la correspondiente subrutina. No existe por tanto una función unlock.

Ejecución

Veamos una ejecución. Notése que el valor final de $locked es siempre 30:

lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ ./shared3.pl 

Maestro: 0:4 0:6 0:10 0:13 0:16 0:19 0:22 0:25 0:28 0:30 
Esclavo: 1:1 1:2 1:7 1:8 1:11 1:14 1:17 1:20 1:23 1:26 
lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ ./shared3.pl 

Maestro: 0:3 0:5 0:8 0:10 0:14 0:16 0:19 0:22 0:24 0:26 
Esclavo: 1:1 1:6 1:11 1:12 1:17 1:20 1:27 1:28 1:29 1:30 
lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ ./shared3.pl 

Maestro: 0:2 0:4 0:7 0:10 0:13 0:16 0:19 0:23 0:25 0:28 
Esclavo: 1:5 1:8 1:11 1:14 1:17 1:20 1:21 1:26 1:29 1:30 
lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ ./shared3.pl 

Maestro: 0:2 0:5 0:9 0:11 0:14 0:17 0:19 0:22 0:24 0:27 
Esclavo: 1:3 1:6 1:7 1:12 1:15 1:20 1:25 1:28 1:29 1:30

Si se comentan las llamadas al método lock se pierde la atomicidad al acceso y el resultado final en la variable locked puede cambiar:

lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ ./nolock.pl 

Maestro: 0:3 0:6 0:9 0:12 0:15 0:18 0:20 0:23 0:26 0:29 
Esclavo: 1:1 1:4 1:7 1:12 1:13 1:18 1:21 1:24 1:27 1:30 
lhp@nereida:~/Lperl/src/properldebugging/Chapter10$ ./nolock.pl 

Maestro: 0:2 0:5 0:8 0:10 0:12 0:14 0:17 0:20 0:23 0:26 
Esclavo: 1:5 1:6 1:11 1:12 1:15 1:18 1:21 1:24 1:27 1:28



Subsecciones
Casiano Rodríguez León
2010-03-22
use strict; 3 use Template; 4 use CGI qw(:all delete_all); 5 use CGI::Carp qw(carpout fatalsToBrowser); 6 use Proc::Background; 7 use Cache::FileCache; 8 use Digest::MD5; 9 10 11 BEGIN { 12 open (my $LOG,'>>/tmp/cgisearch.errors') || die "couldn't open file: $!"; 13 carpout($LOG); 14 } 15 16 # Templates 17 my $result_tt = 'results.html'; # For results 18 my $form_tt = 'form'; # For the form 19 my $wrapper = 'page'; # General wrapper 20 21 $|++; 22 23 # To avoid the message 'Insecure $ENV{PATH} while running with -T' 24 $ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"; 25 26 my $seconds = 6; 27 my $refreshseconds = 3; 28 29 my $form_val = { 30 title => 'Traceroute', 31 head => 'Traceroute', 32 action => script_name(), 33 question => '', 34 submit => 'traceroute to this host', 35 name => 'host', 36 }; 37 38 if (my $session = param('session')) { 39 continue_session($session) 40 } 41 elsif (my $host = param('host')) { 42 new_session($host); 43 } 44 else { 45 show_form($form_tt, $form_val); 46 } 47 exit 0; 48 49 sub show_form { 50 my ($form_tt, $vars) = @_; 51 52 my $template = Template->new( WRAPPER => $wrapper ); 53 print header(-charset => 'utf-8' ); 54 $template->process($form_tt, $vars); 55 } 56 57 sub get_cache_handle { 58 59 Cache::FileCache->new 60 ({ 61 namespace => 'tracerouter', 62 username => 'nobody', 63 default_expires_in => '30 minutes', 64 auto_purge_interval => '4 hours', 65 }); 66 } 67 68 sub get_session_id { 69 Digest::MD5::md5_hex(Digest::MD5::md5_hex(time().{}.rand().$$)); 70 } 71 72 sub continue_session { # returning to pick up session data 73 my $session = shift; 74 75 my $cache = get_cache_handle(); 76 my $data = $cache->get($session); 77 unless ($data and ref $data eq "ARRAY") { # something is wrong 78 show_form($form_tt, $form_val); 79 exit 0; 80 } 81 82 my $template = Template->new(); 83 84 my $finished = $data->[0]; 85 print header(-charset => 'utf-8' ); 86 my $vars = { 87 finished => $finished, 88 title => "Traceroute Results", 89 refresh => ($finished ? "" : "<meta http-equiv=refresh content=$refreshseconds>"), 90 header => 'Traceroute Results', 91 continue => ($finished ? "FINISHED" : "... CONTINUING ..."), 92 message => $data->[1], 93 %$form_val, # Insert form parameters 94 }; 95 $template->process($result_tt, $vars); 96 } 97 98 sub new_session { # returning to select host 99 my $host = shift; 100 101 if ($host =~ /\A([\w.-]{1,100})\Z/) { # create a session 102 $host = $1; # untainted now 103 my $session = get_session_id(); 104 my $cache = get_cache_handle(); 105 $cache->set($session, [0, ""]); # no data yet 106 107 if (my $pid = fork) { # parent does 108 delete_all(); # clear parameters 109 param('session', $session); 110 print redirect(self_url()); 111 } elsif (defined $pid) { # child does 112 close STDOUT; # so parent can go on 113 my $F; 114 unless (open $F, "-|") { 115 open STDERR, ">&=1"; 116 exec "timed-process", $seconds, "/usr/sbin/traceroute", $host; 117 die "Cannot execute traceroute: $!"; 118 } 119 my $buf = ""; 120 while (<$F>) { 121 $buf .= $_; 122 $cache->set($session, [0, $buf]); 123 } 124 $cache->set($session, [1, $buf]); 125 exit 0; 126 } else { 127 die "Cannot fork: $!"; 128 } 129 } else { 130 show_form($form_tt, $form_val); 131 } 132 }

El Template con el Formulario

$ cat -n form
 1  <h1>[% head %]</h1>
 2  <form method="post" action="[% action %]" enctype="multipart/form-data">
 3  [% question %] <input type="text" name="[% name %]" />
 4  <input type="submit" value="[% submit %]">
 5  </form>

Wrapper

$ cat -n page
 1  <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
 2  <head>
 3  <title>[% title %]</title>
 4  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 5  </head>
 6  <body bgcolor = [% bgcol %]>
 7  [% content %]
 8  <hr>
 9  </body>
10  </html>

Véase También

Véase Watching long processes through CGI de Randall Schwartz (Merlin). Este ejemplo es una modificación.



Subsecciones
Casiano Rodríguez León
2011-02-07
A NAME="tex2html8" HREF="http://www.google.es/">googleetsiiullpcgullLHPLHP moodleperlcriticpbpblogsgoogle code project hosting
Sig: Práctica: Control de Procesos Sup: CGI Ant: Depuración con ptkdb y
Casiano Rodríguez León
2011-02-21
asiano Rodríguez León
2011-02-21 HTML>