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";
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.
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.
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).
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:
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
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
.
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