viernes, 22 de julio de 2016

Threads con Harbour para novatos






Voy a publicar una serie de post sobre las funciones de threads que espero os sirva para comprender como podeis sacar provecho a programar usando threads
 
Vamos a empezar a usar threads con Harbour, y lo primero que tenemos que saber que para ello tenemos que compilar con soporte para threads.
Haciendo uso de hbmk2 , simplemente, le pasamos -mt  a la hora de compilar.

Bien, ahora ya estamos preparados para tener acceso al uso de threads.

¿ Pero que un thread y que nos aporta en nuestras aplicaciones ?
Definición simple: Los threads nos permiten hacer varias cosas a la vez. Threads Wikipedia

Un caso real de éxito, sobre un software realizado en Clipper, pasarlo a Harbour 32 bits, y después a usar hilos, fue realizado en un par de días.

Inicialmente ese software estaba en Clipper , que lo ha hace es procesar peticiones según se van dejando ficheros en un directorio, viendo por encima, es similar a esto;

while  .t.
 aFilesDirectory := adir()
  for n := 1 to len( aFilesDirectory )
      ? “Procesa fichero de peticion”
      Procesa( aFilesDirectory[ n ] )
  next
  deletefiles( aFilesDirectory )
end while

Al pasarlo a 32 bits, fue trivial, recompilar y punto.

Pero este sistema tiene un gran handicap, y es que cuando se procesa una petición, el programa no puede atender a otras peticiones, por lo que el tiempo de las peticiones se suman.

Así si tenemos 5 peticiones, y cada una tarda 10 segundos, vemos que la última será procesada 
10 x 5 = 50 segundos después.

¿Y si os digo que usando threads tardaremos 10 segundos en procesar todas las peticiones ? 
Os veo la cara iluminada ;-)
El código de arriba, se convierte usando threads;

while  .t.
 aFilesDirectory := adir()
  for n := 1 to len( aFilesDirectory )
      ? “Procesa fichero de peticion”
     hb_threadStart( @Procesa(),  aFilesDirectory[ n ] )
  next
  deletefiles( aFilesDirectory )
end while


Este software funciona hoy en día con un rendimiento excepcional.
Quien diría que un software en Clipper de hace 10 años, funcionando  en 32bits con Threads.

Ahora, también nos interesa saber si el ejecutable cumple con esta condición, usando simplemente la función hb_mtvm()

IF hb_mtvm()
  ? “Soporte de Threads”
ENDIF

A continuación , vamos a ver la primera función , que no es ni más ni menos que como crear un hilo  ( intentaré usar los ejemplos que están en Harbour /test/mt/ );

hb_threadStart( <@sStart()> | <bStart | “sStart” > [, <params,...> ] ) -> <pThID>

Esta función simplemente crea y ejecuta la función, @sStart()y nos devuelve un id.
Un ejemplo simple, seria poner un simple reloj;

proc main()
  
  cls
  hb_threadStart( @thFunc() )
  
  @1,1 SAY "Pulsa tecla para salir"
  inkey( 0 )
 
return

proc thFunc()

  while .t.
      DispOutAt( 2, 1, Time() )
      hb_idleSleep( 1 )
  end
  
return
  • usar PROCEDURE si la función no retorna nada, simplemente es más optimizado.

hb_threadStart permite llamar igualmente de estas maneras a la función;
  • hb_threadStart( @thFunc() )
  • hb_threadStart( "thFunc" )
  • hb_threadStart( {|| thFunc() } )

Es exactamente lo mismo. Normalmente se usa @thFunc(), por ser la primera implementación y al ser un puntero, creo que es más óptimo a nivel de VM. ( VM = Virtual Machine )

Ya tenemos nuestra aplicación usando threads. Como vemos, la cosa es bien sencilla,
de momento ;-)

Ahora , también a la hora de crear un thread , podemos definir la visibilidad de las variables
dentro del hilo.  Fichero hbthread.ch

#define HB_THREAD_INHERIT_PUBLIC    1
Indica que las variables  públicas son compartidas por todos los hilos

#define HB_THREAD_INHERIT_PRIVATE   2
Indica que las variables  privadas son compartidas por todos los hilos

#define HB_THREAD_INHERIT_MEMVARS   3
Indica que las variables privadas y públicas son compartidas por todos los hilos

#define HB_THREAD_MEMVARS_COPY      4
Indica que lo que se envía es una copia , no la variable en sí.

hb_threadStart( HB_THREAD_INHERIT_PUBLIC, @Process(), Self ) )
otro ejemplo ;
hb_threadStart( HB_BITOR( HB_THREAD_INHERIT_MEMVARS + ;
                         HB_THREAD_MEMVARS_COPY ), ;
                         @thFunc() )

Un ejemplo de esto es el ejemplo en  harbour/tests/mt/mttest08.prg
A tener en cuenta, es que cuando creamos un hilo, las variables PUBLIC que se creen en ese hilo , solo es visible en ese hilo o en hilos hijos, el padre no sabe de su existencia.

Y recordad que el acceso a escribir en variables compartidas por hilos debe ser protegido por el usuario, pero de momento, no te preocupes, lo veremos más adelante, con los mutex.

Ah, y otra cosa súper interesante , y es que el ámbito de una tabla dbf abierta, pertenece en el hilo que se abrió, usando el mismo alias en hilos separados.
Esto, a la hora de portar código existente , como el caso anteriormente explicado, nos ahorra horas y horas de portabilidad de un sistema monolítico a un sistema con threads.

Siguiente post :   hb_threadJoin( )

No hay comentarios:

Publicar un comentario

Android y Git. Disponer del hash automáticamente.

Una de las cosas a las que estoy acostumbrado, es tener siempre en mi código, el hash/tag/versión del control de versiones que estoy usan...