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
Licencia de Creative Commons
Programación Distribuida y Mejora del Rendimiento
por Casiano Rodríguez León is licensed under a Creative Commons Reconocimiento 3.0 Unported License.

Permissions beyond the scope of this license may be available at http://campusvirtual.ull.es/ocw/course/view.php?id=44.
2012-06-19