This entry is part 2 of 3 in the series: Programación Multihilo en Delphi

Introducción

Si no lo has leido ya, y eres relativamente nuevo al mundo de la programación multihilo es recomendable empezar leyendo la Introducción a la programación multihilo para poder decidir correctamente si realmente es necesario implementar un sistema multihilo o no.

Definiendo nuestro hilo

Delphi facilita mucho la creación de hilos de ejecución proporcionando una clase base que podemos heredar para definir nuestras tareas deejecución. Esta clase es la clase TThread.

Un ejemplo de una aplicación que usa una tarea para comprimir un archivo.

// Heredamos la clase TThread y definimos lo que queremos que
// el hilo haga haciendo un override del metodo Execute

type TMiThread = class(TThread)

 private
   FFileName : String;
 public
   // El constructor de la clase
     // CreateSuspended : Si la tarea se crea suspendida
   FileName : El fichero sobre el que se aplica la tarea
  
   constructor Create(CreateSuspended : boolean;
                      FileName : string); override;
   procedure Execute; override;

end;

TForm1 = class(TForm)

   BtnComprimir: TButton;
   procedure BtnComprimirClick(Sender: TObject);
 private
   // Private declarations
   thr1, thr2 : TMyThread;
 public
   // Public declarations

end;

constructor TMiThread.Create(CreateSuspended : boolean); 
begin

 // Inicializar los que queramos
 FFileName = FileName;
 inherited Create(CreateSuspended);

end;

procedure Execute; 
begin

 // El código que ejecutará la tarea
 // Por ejemplo comprimir el fichero pasado

end;

procedure TForm1.OnBtnComprimirClick(Sender : TComponent); 
begin

 // Creamos la tarea suspendida
 thr1 = TMyThread.Create(true,"MiFichero.txt");
 thr2 = TMyThread.Create(true,"Otro.txt");
 // Iniciar las tareas
 thr1.Resume;
 thr2.Resume;
 // Esperar a que acaben
 if not thr1.Terminated then
   thr1.WaitFor;
 if not thr2.Terminated then
   thr2.WaitFor;

end;

En el código anterior podemos observar con que basta con crear una clase que herede de la clase TThread y realizar un override del metodo Execute poniendo en él el código que queramos que realice la tarea. Este código suele seguir generalmente este patron:

procedure Execute;
begin

 // Ejecutar algún proceso repetitivo hasta que la tarea termine }
 while not Self.Terminated do
 begin
    RealizarTrabajo;
 end;
 // Esto puede ir aqui (si los recursos son privados)
 // o en el destructor (si otro hilo quiere acceder al resultado)

 FinalizarRecursos; 
end;

Cuando se llama al metodo Terminate de la clase TThread, el valor de Terminated se pone a true y la función Execute se sale. Aunque existen formas de «matar» hilos mediante el API de windows la forma más común (y más recomendable) de actuación es «solicitar» al hilo su terminación (generalmente desde el hilo principal) llamanda al metodo Terminate y después quedarse a la espera de que el hilo finalice (llamando al metodo WaitFor)

De esta forma podemos ver que la creación de hilos en Delphi es una cosa muy sencilla y que, de forma rápida, podemos distrubir la carga de trabajo entre varias tareas.

Sincronización básica

Uno de las complicaciones de la programación multihilo es la problemática asociada al acceso simultaneo a las variables compartidas y la necesidad de sincronizar dichos accesos de forma que no haya conflictos.

La problemática de los acceso a variables compartidas es un problema común proveniente de la necesidad de atomicidad en algunas operaciones. Por ejemplo, analizando la operación

 i := i + 1;

que incrementa una variable global i en uno, (que por otro lado es uno de los ejemplos más típicos que se pueden poner) observamos que, si dos hilos de ejecucíón intentan ejecutar el código simultaneamente podría pasar que:

  • Entra el hilo 1 y lee el valor de i, por ejemplo 7, (y lo almacena en un registro de máquina)
  • Justo en ese momento se produce un cambio de contexto y entra a ejecutar el hilo 2
  • El hilo 2 lee el valor de i (7), le suma 1 y lo almacena
  • Vuelve a entrar el hilo 1, recupera el valor del registro de la máquina (7), le suma 1 y lo almacena

El resultado final obtenido será 8 cuando lo que esperariamos obtener si entrarán dos hilos cuando el valor es 7 sería 9.

Una forma básica de solucionar este problema es utilizar el metodo Synchronize de la clase TThread. Dicho metodo produce la ejecución del metodo especificado desde el hilo de ejecución principal de forma que no haya conflictos. De esta forma la llamada anterior quedaría como:

procedure IncrementaI; begin

 i := i + 1;

end;

procedure PulsaBoton; begin

 Button1.Click();

end;

procedure TMyThread.Execute;
begin

 // ........... Código de ejecución
 // ..............................
 // Aqui incremento i
 Self.Synchronize(IncrementaI);
 // Y pulso el boton del formulario
 Self.Synchronize(PulsaBoton);

end;

De esta forma evitamos cualquier tipo de problema en la sincronización de acceso a los recursos.

Nota Final:

Casi todos los componentes VCL de delphi (botones, formularios, treeviews, etc) no son ThreadSafe, es decir, no soportan ser invocados desde distintos hilos, por ello, toda llamada a algún metodo de un componente debe ser protegída mediante el metodo Synchronize.

Series Navigation

<< Introducción a la programación multihiloAplicaciones Multihilo en Delphi. Primitivas de sincronización de Windows >>