lunes, 25 de noviembre de 2013

Hilos #concurrentes en #perl

En esta oportunidad hablaré de como ejecutar rutinas de forma concurrente.

No pretendo abarcar demasiado explicando que es esto. Una explicación detallada puede ser conseguida en: http://es.wikipedia.org/wiki/Hilo_en_sistemas_operativos, por acá solo me limitaré a decir que es una manera de ejecutar rutinas en perl con la cual se aprovechan las bondades de multinucleo de los procesadores, permitiendo ejecutar una rutina varias veces de forma paralela.

Dicho lo anterior,

Usaremos un ejemplo bien simple, contar los números del 1 al 10 aguardando 1 segundo cada vez. Un programa básico sería:

#!/usr/bin/perl
use strict;

cuenta(1,5,1);

sub cuenta
{
        my ($inicio,$final,$paso) = @_;
        for(my $i=$inicio;$i<=$final;$i+=$paso){
            print "$i\n";
            sleep(1);
        }
}

Ahora comenzaremos a hacerlo con concurrencias, para poder hacer esto usaremos el módulo threads. Primero vamos a hacer un poco de teoría.

El módulo threads te permite crear procesos concurrentes, dichos procesos pueden ejecutarse de dos formas:

  1. "joinnable": El proceso se ejecutará y terminará, pero se quedará a la espera para poder retornar un valor del proceso ejecutado. La ventaja de este tipo de threads es la posibilidad de listarlas directamente mediante el uso del método list lo veremos más adelante
  2. "detached" : El proceso se ejecutará y terminará sin más.

Teniendo lo anterior claro vamos a crear nuestro primer programas con threads.

#!/usr/bin/perl
use strict;
use threads; 

my $thread = threads->create('cuenta',1,5,1);
$thread->join();

sub cuenta
{
        my ($inicio,$final,$paso) = @_;
        for(my $i=$inicio;$i<=$final;$i+=$paso){
            print "$i\n";
            sleep(1);
        }
}

Hasta ahora nada especial, no se aprecia la magia vamos a realizar unos pequeños cambios y vamos a colocar a contar a una segunda función:

#!/usr/bin/perl
use strict;
use threads;

my $thread1=threads->create('cuenta',1,5,1); #Cuenta de 1 al 5
my $thread2=threads->create('cuenta',10,15,1); #Cuenta de 10 a 15

while($thread1->is_running() || $thread2->is_running()){
    sleep 1;
}
$thread1->join(); $thread2->join(); #Hay que finalizar los threads si el programa termina los anulará sino me creen comenten esta linea.

#Mismo método cuenta copiarlo del anterior script.

Podrán apreciar que tienen dos cuentas en paralelo, una contando de 1 a 5 y otra contando de 10 a 15. Ahora veamos con el detach, realmente no hay mayor cambio salvo que hay que ejecutar el detach después de llamar generar los hilos:

#!/usr/bin/perl
use strict;
use threads;

my $thread1=threads->create('cuenta',1,5,1); #Cuenta de 1 al 5
my $thread2=threads->create('cuenta',10,15,1); #Cuenta de 10 a 15

$thread1->detach(); $thread2->detach(); #Se eliminan los vinculos al thread, lo único que nos siguen vinculando son thread1 y thread2

while($thread1->is_running() || $thread2->is_running()){
    sleep 1;
}


#Mismo método cuenta copiarlo del anterior script.

Ok no se entiende que ha sucedido o cual es la diferencia. Realmente puesto como hasta ahora no hay gran cosa, el siguiente ejemplo va a clarificar un poco más la situación.

Vamos a contar, pero en esta oportunidad vamos a replantear como lo hacemos fíjense que nunca hemos tocado la función contar:

#!/usr/bin/perl
use strict;
use threads;
threads->create('cuenta',1,5,1); #Cuenta de 1 al 5 #Se crea el thread pero no se guarda la referencia
threads->create('cuenta',10,15,1); #Cuenta de 10 a 15

while(threads->list(threads::running)){ #Nos da un listado de las funciones corriendo.
    sleep 1;
}

map { $_->join(); } threads->list(threads::joinable); #Finalizamos las funciones que se encuentran en memoria. 


#Mismo método cuenta copiarlo del anterior script.

En este punto es que el método detach comienza a necesitar más trabajo, porque cuando se hace detach, no tenemos manera directa de consultar como lo hemos hecho acá los threads en ejecución. Una manera de usar el detach sería ir almacenando en un arreglo o hash los threads creado y luego eliminarlos o sacarlos del arreglo al consultar su estado. No obstante no encuentro mayor ventaja con lo planteado anteriormente.

Vamos a complicar el escenario un poco más

Necesitamos contar paralelamente de 1 en 1 hasta el 100 en grupos de 10, es decir, un thread del 1 al 10, otro del 11 al 20, etc, pero con una restricción que no puede haber más de 4 hilos simultáneos a la vez.

#!/usr/bin/perl
use strict;
use threads
for(my $i=0;$i<10;$i++){
    until(threads->list(threads::running)<4){
            print "Hay 4 threads corriendo no se pueden crear más hasta que no se libere 1\n";
            sleep 1;
    }
    print "Hay threads disponibles procediendo a crear el thread Numero: ",$i+1,"\n";
    threads->create('cuenta',($i*10)+1,($i+1)*10,1);
    map { $_->join(); } threads->list(threads::joinable); #Se terminan los threads que han finalizados
}

while(threads->list(threads::running)){ #Esperamos que finalicen los threads que pudieron quedar en ejecución en memoria.
    sleep 1;
}
map { $_->join(); } threads->list(threads::joinable);

#Misma función contar 

No se podría hacer así de fácil con el detach, pero bueno, dejo la implementación del detach en una estructura dinámica parecida a la que hice a otro menos flojo que yo :D

No hay comentarios:

Publicar un comentario