Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/es: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Descripción general
22
33   En esta página se tratará de explicar cómo escribir y depurar una aplicación *multihilo* con Free Pascal y Lazarus.
44
55   Una aplicación *multihilo* es aquella que crea dos o más hilos de ejecución que trabajan al mismo tiempo.
66
77   Si eres nuevo en programación *multihilo*, por favor lee el apartado "¿Necesito *multihilo*?" para saberlo realmente. Puedes ahorrarte un montón de dolores de cabeza.
88
99   Uno de los hilos será el Hilo Principal, este es el que crea el Sistema Operativo al arrancar la aplicación.
1010
1111   El Hilo Principal debe ser **el único hilo** que actualice los componentes de la interfaz con el usuario, en caso contrario la aplicación dejará de funcionar.
1212
1313   La idea principal es que la aplicación puede hacer alguna tarea en el fondo (en un segundo hilo), mientras que el usuario puede seguir trabajando (con el hilo principal).
1414
1515   Otro uso de los *hilos* es para tener una aplicación que responda mejor. Si crea una aplicación, y cuando el usuario pulsa un botón, la aplicación inicia un proceso (un gran trabajo) ... y durante el proceso, la pantalla deja de responder, el usuario tendrá la sensación de que la aplicación está muerta, lo cual no es agradable. Si la tarea pesada se ejecuta en un segundo hilo, la aplicación puede seguir respondiendo (o casi) como si estuviera desocupada. En este caso es una buena idea, antes de empezar el hilo, desactivar los botones de la ventana para evitar que el usuario inicie de más de un hilo para ese trabajo.
1616
1717   Otro uso más, es crear un servidor capaz de responder a muchos clientes al mismo tiempo.
1818
1919## ¿Necesitas múltiples hilos?
2020
2121   Si eres novato en esto de los múltiples hilos y lo único que deseas es hacer que tu aplicación responda antes, mientras realiza un trabajo muy pesado, es posible que esto no sea lo que buscas.
2222
2323   Las aplicaciones con múltiples hilos de ejecución son siempre más difíciles de depurar y resultan más complejas. Y muchas veces es innecesario, con un solo hilo es suficiente. Se puede dividir la tarea pesada en varios bloques más pequeños, y usar en el lugar adecuado **Application.ProcessMessages**, que permite a la LCL gestionar los mensajes en espera y continuar después con nuestro código. La idea es realizar una parte del trabajo, llamar a *Application.ProcessMessages* para comprobar si el usuario hace clic en Cancelar o en algún otro sitio o si es necesario repintar la ventana, y luego continuaremos con la siguiente parte del trabajo, llamaremos de nuevo a *Application.ProcessMessages* y así sucesivamente.
2424
2525   Por ejemplo: Leer un fichero muy grande y procesarlo.
2626
2727   Ver ejemplo en \${LazarusDir}/examples/multithreading/singlethreadingexample1.lpi.
2828
2929   La programación con múltiples hilos es necesaria únicamente para
3030
3131- Bloqueo de enlaces, como en las comunicaciones de red
3232- Utilizar múltiples procesadores a la vez
3333- Llamadas a librerías y algoritmos que no pueden ser divididos en partes más pequeñas
3434
3535## La clase TThread
3636
3737   El ejemplo que utilizaremos se puede encontrar en el directorio \${LazarusDir}/examples/multithreading/.
3838
3939   La manera más fácil de crear una aplicación de múltiples hilos es utilizar la clase **TThread**. Esta clase permite la creación de un hilo adicional, junto con el hilo principal, de una manera sencilla.
4040
4141   Por lo general, sólo tienen que sobrescribir dos métodos: el constructor *Create* y el método *Execute*.
4242
4343   En el constructor hay que preparar el hilo para su ejecución. Hay que dar el valor inicial a las variables y propiedades que lo precisen. El constructor original de TThread requiere un parámetro llamado Suspended. Como es de esperar, si *Suspended* es verdadero (*true*) se evitará que el hilo de comience automáticamente tras la creación. Si el valor del parámetro *Suspended* es 'false' el hilo se ejecutará inmediatamente después de la creación.
4444
4545   Si el hilo se crea suspendido, entonces sólo se ejecutará después de llamar al método *Resume*.
4646
4747   Desde la versión 2.0.1 de PFC **TThread.Create** tiene además un parámetro implícito para el tamaño de la pila (*Stack*), con lo que se puede modificar el tamaño de la pila de cada hilo creado si ello es preciso. Un buen ejemplo es la recursión con alto anidamiento de llamadas. Si no se especifica el tamaño de la pila en el parámetro, se utilizará el valor por defecto del SO.
4848
4949   En el método *Execute* sobrescrito se pone el código que deseamos que ejecute el hilo.
5050
5151   La clase TThread tiene una propiedad importante:
5252
5353``` pascal
5454 Terminated : boolean;
5555```
5656
5757    Si el hilo tiene un bucle (y esto es lo corriente) este debe terminar cuando *Terminated* es cierto (*true*), el estado por defecto es 'false'. Por tanto es preciso comprobar el valor de *Terminated*, y si es 'true' salir del método *Execute* tan pronto y limpiamente como sea posible.
5858
5959   Hay que tener en cuenta que el método *Terminate* no hace nada por defecto: el método *Execute* debe realizar explícitamente todas las tareas necesarias para finalizar el trabajo.
6060
6161   Como hemos explicado antes, el hilo no debe interactuar con los componentes visibles. Para mostrar algo al usuario hay que hacerlo en el hilo principal. Para hacer esto, TThread tiene un método llamado *Synchronize*, el cual recibe un parámetro, que consiste en un método de nuestra clase, que a su vez no tiene parámetros. Cuando se llama a este método a través de *Synchronize(@MyMethod)*, la ejecución del hilo se detendrá, el código de MyMethod se ejecutará en el hilo principal y, a continuación se reanudará la ejecución del hilo.
6262
6363El cómo se ejecuta *Synchronize* depende de la plataforma, pero básicamente hace esto: Coloca un mensaje en la cola de mensajes principal y 'se va a dormir'. En su momento, el hilo principal procesa el mensaje y llama a MyMethod. De esta manera se llama MyMethod sin contexto, es decir que no lo hará durante el desplazamiento del ratón o durante el pintado de la ventana, sino después. Después de que el hilo principal ejecuta MyMethod, despierta al hilo del sueño y procesa el siguiente mensaje. En ese momento el hilo continúa su ejecución.
6464
6565   Existe otra propiedad importante de *TThread*: **FreeOnTerminate**. Si esta propiedad está a 'true', el hilo se liberará automáticamente al finalizar la ejecución del método 'Execute', en caso contrario hay que liberarlo explícitamente en el código de la aplicación.
6666
6767   Ejemplo:
6868
6969``` pascal
7070 Type
7171 TMyThread = class(TThread)
7272 private
7373 fStatusText : string;
7474 procedure ShowStatus;
7575 protected
7676 procedure Execute; override;
7777 public
7878 Constructor Create(CreateSuspended : boolean);
7979 end;
8080
8181 constructor TMyThread.Create(CreateSuspended : boolean);
8282 begin
8383 FreeOnTerminate := True;
8484 inherited Create(CreateSuspended);
8585 end;
8686
8787 procedure TMyThread.ShowStatus;
8888 // // este es el método que se llamará desde el hilo principal con Synchronize, para acceder a la interfaz.
8989 begin
9090 Form1.Caption := fStatusText;
9191 end;
9292
9393 procedure TMyThread.Execute;
9494 var
9595 newStatus : string;
9696 begin
9797 fStatusText := 'Arrancando TMyThread ...';
9898 Synchronize(@Showstatus);
9999 fStatusText := 'Ejecutando TMyThread...';
100100 while (not Terminated) and ([otra_condición_requerida]) do
101101 begin
102102 ...
103103 //[aquí el código del hilo principal]
104104 ...
105105 if NewStatus <> fStatusText then
106106 begin
107107 fStatusText := newStatus;
108108 Synchronize(@Showstatus);
109109 end;
110110 end;
111111 end;
112112```
113113
114114   En la aplicación,
115115
116116``` pascal
117117 var
118118 MyThread : TMyThread;
119119 begin
120120 MyThread := TMyThread.Create(True); // De esta manera no se inicia automáticamente
121121 ...
122122 //[Aquí el código de inicio necesario antes de que comience la ejecución de los hilos]
123123 ...
124124 MyThread.Resume;
125125 end;
126126```
127127
128128    Si se desea hacer la aplicación más flexible puede crear un evento para el hilo, de esta manera su método sincronizado no estará ligado a un formulario o a una clase específica; y se podrán usar distintos manipuladores para el evento. Aquí hay un ejemplo:
129129
130130``` pascal
131131 Type
132132 TShowStatusEvent = procedure(Status: String) of Object;
133133
134134 TMyThread = class(TThread)
135135 private
136136 fStatusText : string;
137137 FOnShowStatus: TShowStatusEvent;
138138 procedure ShowStatus;
139139 protected
140140 procedure Execute; override;
141141 public
142142 Constructor Create(CreateSuspended : boolean);
143143 property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
144144 end;
145145
146146 constructor TMyThread.Create(CreateSuspended : boolean);
147147 begin
148148 FreeOnTerminate := True;
149149 inherited Create(CreateSuspended);
150150 end;
151151
152152 procedure TMyThread.ShowStatus;
153153 // este es el método que se llamará desde el hilo principal con Synchronize, para acceder a la interfaz
154154 begin
155155 if Assigned(FOnShowStatus) then
156156 begin
157157 FOnShowStatus(fStatusText);
158158 end;
159159 end;
160160
161161 procedure TMyThread.Execute;
162162 var
163163 newStatus : string;
164164 begin
165165 fStatusText := 'Arrancando MyThread...';
166166 Synchronize(@Showstatus);
167167 fStatusText := 'Ejecutando TMyThread...';
168168 while (not Terminated) and ([otra_condición_requerida]) do
169169 begin
170170 ...
171171 //[aquí el código del hilo principal]
172172 ...
173173 if NewStatus <> fStatusText then
174174 begin
175175 fStatusText := newStatus;
176176 Synchronize(@Showstatus);
177177 end;
178178 end;
179179 end;
180180```
181181
182182   En la aplicación,
183183
184184``` pascal
185185 Type
186186 TForm1 = class(TForm)
187187 Button1: TButton;
188188 Label1: TLabel;
189189 procedure FormCreate(Sender: TObject);
190190 procedure FormDestroy(Sender: TObject);
191191 private
192192 { private declarations }
193193 MyThread: TMyThread;
194194 procedure ShowStatus(Status: string);
195195 public
196196 { public declarations }
197197 end;
198198
199199 procedure TForm1.FormCreate(Sender: TObject);
200200 begin
201201 inherited;
202202 MyThread := TMyThread.Create(true);
203203 MyThread.OnShowStatus := @ShowStatus;
204204 end;
205205
206206 procedure TForm1.FormDestroy(Sender: TObject);
207207 begin
208208 MyThread.Terminate;
209209 MyThread.Free;
210210 inherited;
211211 end;
212212
213213 procedure TForm1.Button1Click(Sender: TObject);
214214 begin
215215 MyThread.Resume;
216216 end;
217217
218218 procedure TForm1.ShowStatus(Status: string);
219219 begin
220220 Label1.Caption := Status;
221221 end;
222222```
223223
224224## Cosas con las que tener especial cuidado
225225
226226    En Windows hay un posible problema cuándo se utilizan hilos conjuntamente con el modificador -Ct (comprobar pila). Por razones no muy claras la comprobación de la pila se dispara con todos los *TThread.Create* si se utiliza el tamaño de pila por defecto. La única solución, por el momento, es simplemente no utilizar el modificador -Ct. Tenga en cuenta que no se producirá una excepción en el hilo principal, pero sí en el hilo recién creado. Es como si el hilo nunca se hubiera iniciado.
227227
228228   Un código adecuado para comprobar si esta u otras excepciones pueden ocurrir en la creación del hilo es:
229229
230230``` pascal
231231 MyThread:=TThread.Create(False);
232232 if Assigned(MyThread.FatalException) then
233233 raise MyThread.FatalException;
234234```
235235
236236   Este código asegura que cualquier excepción que se produzca durante la creación de un hilo será lanzada en su hilo principal.
237237
238238## Unidades necesarias para una aplicación de múltiples hilos
239239
240240   No es necesaria ninguna unidad específica para trabajar en Windows. Sin embargo, con Linux, MacOSX y FreeBSD, será necesaria la unidad **cthreads** que **debe** ser la primera en la cláusula **uses** del archivo de programa, .lpr del proyecto.
241241
242242   Por lo tanto, el código de la aplicación Lazarus debería ser algo así:
243243
244244``` pascal
245245 program MiProgramaMultiHilo;
246246 {$mode objfpc}{$H+}
247247 uses
248248 {$ifdef unix}
249249 cthreads,
250250 cmem, // el administrador de memoria de c es en algunos sistemas mucho más rápido
251251 {$endif}
252252 Interfaces, // this includes the LCL widgetset
253253 Forms
254254 { add your units here },
255255```
256256
257257   Si no lo haces así, y utilizas TThread, obtendrás este error en el arranque:
258258
259-` `*`This binary has no thread support compiled in.`*\
260-` `*`Recompile the application with a thread-driver in the program uses clause before other units using thread.`*
259+``` pascal
260+ This binary has no thread support compiled in.
261+ Recompile the application with a thread-driver in the program uses clause before other units using thread.
262+```
261263
262264   Nota:
263265
264266- Si obtienes un error de enlace de `"mcount" not found` es que se utiliza otra unidad que contiene código de multiproceso y es necesario agregar la unidad cthreads o usar enlace inteligente.
265267- Si recibes el error: `"Project raised exception class 'RunError(232)"` en el procedimiento SYSTEM_NOTHREADERROR, es que el código requiere multihilo y hay que añadir la unidad cthreads.
266268
267269### Múltiples hilos en paquetes
268270
269271   Los paquetes que usan múltiples hilos debe añadir el indicador **-dUseCThreads** en las opciones del compilador. Abra el paquete, y en el editor, pulsa el botón 'Opciones', en la pestaña 'Uso' y agrega *-dUseCThreads* en la opción 'Personalizado' . Con esta operación se definirá esta opción para todos los proyectos y paquetes que utilicen este paquete, incluyendo el IDE. El IDE y todas las nuevas aplicaciones creadas por el IDE tendrán entonces el siguiente código en su archivo .lpr :
270272
271273``` pascal
272274 uses
273275 {$IFDEF UNIX}{$IFDEF UseCThreads}
274276 cthreads,
275277 {$ENDIF}{$ENDIF}
276278```
277279
278280## Soporte para Sistema de Multiproceso Simétrico (*SMP*)
279281
280282   Las buenas noticias es que si tu aplicación funciona correctamente con múltiples hilos de esta forma, está también preparado para SMP.
281283
282284## Depurando aplicaciones de múltiples hilos con Lazarus
283285
284286   La depuración en Lazarus necesita GDB y está siendo rápidamente más y más funcional y estable. No obstante, hay distribuciones Linux con algunos problemas.
285287
286288### Salida de la depuración
287289
288290   En una aplicación de un único hilo simplemente se escribe en la consola o terminal y las líneas son escritas en el orden adecuado. En una aplicación de múltiples hilos las cosas son más complicadas. Si dos hilos están escribiendo, por ejemplo una línea escrita por un hilo A antes que una línea del hilo B, no son necesariamente escritas en ese orden. Puede incluso suceder que un hilo escriba su salida, mientras el otro hilo está escribiendo una línea.
289291
290292   La unidad *LCLProc* contiene varias funciones, para que cada hilo escriba su propio archivo de registro:
291293
292294``` pascal
293295 procedure DbgOutThreadLog(const Msg: string); overload;
294296 procedure DebuglnThreadLog(const Msg: string); overload;
295297 procedure DebuglnThreadLog(Args: array of const); overload;
296298 procedure DebuglnThreadLog; overload;
297299```
298300
299301   Por ejemplo: En lugar de *writeln('Algo de texto ',123);* utiliza *DebuglnThreadLog(\['Algo de texto ',123\])*;, de esta manera se añade la línea 'Algo de texto 123' al archivo **Log\<PID\>.txt**, donde PID es el ID del hilo actual.
300302
301303   Es una buena idea eliminar los archivos de registro después de cada ejecución:
302304
303-` rm -f Log* && ./project1`
305+```text
306+ rm -f Log* && ./project1
307+```
304308
305309### Linux
306310
307311   Si intentas depurar una aplicación de múltiples hilos en Linux, tendrás un gran problema: el servidor X se colgará.    Si te ocurre esto, simplemente sal de la sesión y crea una nueva presionando CTRL+ALT+F3. Esto abre una sesión de consola, en ella escribimos **sudo /etc/init.d/gdm restart**; con esto rearrancamos el gestor gráfico de escritorio, y regresamos al mismo.
308312
309313   Una forma de resolver este problema en ubuntu x64 es configurar las opciones de proyecto en depuración para requerir un fichero de información adicional...
310314
311-` Proyecto -> Opciones del Compilador... -> Enlazando -> Depurando: marcar la opción `*`Usar archivo externo de símbolos de depuración para gdb (-Xg)`*`.`
315+```text
316+ Proyecto -> Opciones del Compilador... -> Enlazando -> Depurando: marcar la opción Usar archivo externo de símbolos de depuración para gdb (-Xg).
317+```
312318
313319   Si lo anterior no funciona prueba este remedio:
314320
315321   Crear una nueva instancia del servidor X con:
316322
317-` X :1 &`
323+```text
324+ X :1 &
325+```
318326
319327   Se abrirá un escritorio gráfico, y podrás cambiar al escritorio en que estabas trabajando pulsando CTRL + ALT + F7, y volver al nuevo escritorio con CTRL + ALT + F8 (si esta combinación no funciona, prueba con CTRL + ALT + F2 ... ésto funciona en Slackware)
320328
321329   Ahora se puede, si se desea, crear una sesión de escritorio en el servidor X recién arrancado con:
322330
323-` gnome-session --display=:1 &`
331+```text
332+ gnome-session --display=:1 &
333+```
324334
325335   Luego, en Lazarus, en los parámetros de ejecución del proyecto en la pestaña Local, marcar "Usar pantalla" y escribimos ':1'.
326336
327337   Ahora la aplicación se ejecutará en el segundo servidor X y podremos depurar en el primero.
328338
329339   Los procedimientos señalados ha sido probados con FreePascal 2.0 y Lazarus 0.9.10 en Windows y Linux.
330340
331341------------------------------------------------------------------------
332342
333343   En lugar de crear una nueva sesión de X, se puede utilizar [Xnest](http://en.wikipedia.org/wiki/Xnest). Xnest es una sesión X en una ventana. Usándola el servidor X no se bloquea al depurar los hilos, y es mucho más fácil depurar sin tener que cambiar entre terminales.
334344
335345   La segunda línea para ejecutar Xnest es
336346
337-` Xnest :1 -ac`
347+```text
348+ Xnest :1 -ac
349+```
338350
339351   para crear una sesión X en :1, y deshabilitar el control de acceso.
340352
341353## Artefactos *Widgetsets*
342354
343355   Las interfaces de *win32*, la de *gtk* y la de *carbon* soportan completamente la programación con múltiples hilos. Esto significa, que la clase *TThread*, las secciones críticas (*TCriticalSection*) y la sincronización (*Synchronize*) funcionarán.
344356
345357   Pero los subprocesos no son seguros. Esto significa que sólo un hilo a la vez puede acceder a la LCL. Y puesto que el hilo principal no debe esperar a otro hilo, sólo al hilo principal se le permite acceder a la LCL, lo que significa que se ocupará de cualquier cosa que tenga que ver con TControl, Application o los componentes de la LCL.
346358
347359   Hay algunas funciones de hilo seguro en la LCL. Por ejemplo la mayoría de las funciones de la unidad *FileUtil* son seguras para subprocesos.
348360
349361## Secciones Críticas
350362
351363   Una *sección crítica* es un objeto utilizado para hacer asegurar que cierto código es ejecutado únicamente por un único hilo en un momento dado. La sección círitica debe ser creada e iniciada antes de ser utilizada y debe ser liberada cuándo deje de ser necesaria.
352364
353365   Las secciones críticas se utilizan habitualmente de esta forma:
354366
355367   Añadir la unidad *SyncObjs*.
356368
357369   Declarar la sección globalmente para todos los hilos que deban acceder a la sección:
358370
359371``` pascal
360372 MyCriticalSection: TRTLCriticalSection;
361373```
362374
363375   Crear e iniciar la sección:
364376
365377``` pascal
366378 InitializeCriticalSection(MyCriticalSection);
367379```
368380
369381   Ejecutar algunos hilos. Hacer algo que necesite exclusividad
370382
371383``` pascal
372384 EnterCriticalSection(MyCriticalSection);
373385 try
374386 // acceder a algunas variables, escribir archivos, enviar algunos paquetes de red, etc.
375387 finally
376388 LeaveCriticalSection(MyCriticalSection);
377389 end;
378390```
379391
380392   Después de terminar todos los hilos, liberar la sección crítica:
381393
382394``` pascal
383395 DeleteCriticalSection(MyCriticalSection);
384396```
385397
386398   Como alternativa, puede utilizar un objeto *TCriticalSection*. La creación del objeto realiza el inicio, el método *Enter* realiza la función de *EnterCriticalSection*, el método *Leave* ejecuta *LeaveCriticalSection* y la destrucción del objeto lleva a cabo la terminación y liberación de recursos.
387399
388400   Por ejemplo: 5 hilos incrementan un contador. Ver \${LazarusDir}/examples/multithreading/criticalsectionexample1.lpi
389401
390402   **Cuidado**: Hay dos conjuntos de las 4 funciones mencionadas. Un conjunto de la RTL y otra de la LCL. Las funciones de la LCL están definidas en las unidades *LCLIntf* y *LCLType*. Ambos conjuntos trabajan de forma muy parecida. Puede utilizar ambas al mismo tiempo en su aplicación, pero no se debe utilizar una función de RTL con una sección crítica de la LCL, y a la inversa.
391403
392404### Compartiendo variables
393405
394406   Si varios hilos comparten una variable, que es de sólo lectura, no hay de que preocuparse, simplemente léela. Pero si uno o varios hilos cambian el valor de la variable, entonces hay que asegurar que únicamente un hilo accede a la variable en un momento dado.
395407
396408   Por ejemplo: 5 hilos incrementan un contador. Ver \${LazarusDir}/examples/multithreading/criticalsectionexample1.lpi
397409
398410## Esperando a otro hilo
399411
400412   En caso de que un hilo A necesite un resultado de otro hilo B, deberá esperar hasta que B termine su ejecución.
401413
402414   **Importante:** El hilo principal nunca debe esperar a otro hilo. En lugar de eso utilice *Synchronize* (ver más arriba)
403415
404416   Véase, por ejemplo: \${LazarusDir}/examples/multithreading/waitforexample1.lpi
405417
406418``` pascal
407419 { TThreadA }
408420 procedure TThreadA.Execute;
409421 begin
410422 Form1.ThreadB:=TThreadB.Create(false);
411423 WaitForB:=RTLEventCreate; //Crear el evento
412424 while not Application.Terminated do begin
413425 RtlEventWaitFor(WaitForB); // esperar indefinidamente (hasta que A es despertado por B)
414426 writeln('A: Contador del hilo B='+IntToStr(Form1.ThreadB.Contador));
415427 end;
416428 end;
417429
418430 { TThreadB }
419431 procedure TThreadB.Execute;
420432 var
421433 i: Integer;
422434 begin
423435 Contador:=0;
424436 while not Application.Terminated do begin
425437 // B: trabajando ...
426438 Sleep(1500);
427439 inc(Contador);
428440 // despertar A
429441 RtlEventSetEvent(Form1.ThreadA.WaitForB);
430442 end;
431443 end;
432444```
433445
434446## Réplica (*Fork*)
435447
436448   Cuándo replicamos una aplicación con múltiples hilos, hay que tener presente que los hilos han de ser creados y estar en funcionamiento **antes** de realizar la replica (fork o fpFork, ya que **no** pueden ejecutarse en el proceso hijo. Como se indica en el manual de *fork()*, el estado de los los hilos que se ejecutan antes de la bifurcación será indefinido.
437449
438450   Por lo tanto, hay que tener en cuenta que cualquier hilo iniciado antes de la llamada (incluidos los de la sección de inicio), no va a funcionar.
439451
440452## Computación distribuida
441453
442454   El siguiente gran paso, después de programar múltiples hilos, es hacer que estos se ejecuten en múltiples máquinas.
443455
444456- Se puede usar algún paquete de TCP como *synapse*, *lnet* o *Indy* para las comunicaciones. Esto proporciona la máxima flexibilidad y se usa principalmente para aplicaciones cliente / servidor con conexiones ligeras.
445457- Se puede usar librerías de envío de mensajes, como [[MPICH]], que se utilizan para Computación de Altas Prestaciones (HPC, *High Performance Computing*) sobre grupos de ordenadores (*clusters*).
446458
447459## Hilos externos
448460
449461   Para hacer que el sistema de hilos de Free Pascal funcione correctamente, cada nuevo hilo FPC tiene que ser iniciado (más exactamente, las excepciones, el sistema de E/S y el sistema *threadvar* por hilo tiene que ser iniciado ya que *threadvars* y la pila ya están trabajando). Esto es totalmente automático si utilizas BeginThread (o indirectamente mediante la clase TThread). Sin embargo, si utilizas los hilos que se crearon sin BeginThread (es decir, hilos externos), trabajo adicional (actualmente) podría ser necesario. En hilos externos se incluyen los que fueron creados en librerías C externas (.DLL/.so).
450462
451463   Cosas ha considerar cuando se utilizan hilos externos (podría no ser necesario en todas o en futuras versiones del compilador):
452464
453465- No utilices hilos externos en absoluto; usa hilos FPC. Si puedes conseguir el control sobre cómo el hilo se crea, crea el hilo por tí mismo mediante el uso de BeginThread.\
454466
455467Si la convención de llamada no se ajusta (v.g. si tu función original del hilo necesita llamadas con convención cdecl, pero BeginThread necesita convención Pascal, crea un registro, guarda la función requerida original de hilos en ella, y llama a esa función en la función pascal del hilo:
456468
457469``` pascal
458470 type
459471 TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
460472
461473 PCdeclThreadFuncData = ^TCdeclThreadFunc;
462474 TCdeclThreadFuncData = record
463475 Func: TCdeclThreadFunc; //cdecl function
464476 Data: Pointer; //original data
465477 end;
466478
467479 // el hilo pascal llama al función cdecl
468480 function C2P_Translator(FuncData: pointer) : ptrint;
469481 var
470482 ThreadData: TCdeclThreadFuncData;
471483 begin
472484 ThreadData := PCdeclThreadFuncData(FuncData)^;
473485 Result := ptrint(ThreadData.Func(ThreadData.Data));
474486 end;
475487
476488 procedure CreatePascalThread;
477489 var
478490 ThreadData: PCdeclThreadFunc;
479491 begin
480492 New(ThreadData);
481493 // esta es la función cdecl deseada hilo
482494 ThreadData^.Func := func;
483495 ThreadData^.Data := user_data;
484496 // this creates the Pascal thread
485497 BeginThread(@C2P_Translator, ThreadData );
486498 end;
487499```
488500
489501- Inicia el sistema de hilos de FPC mediante la creación de un hilo vacío. Si no creas ningún hilo Pascal en tu aplicación, el sistema de hilos no se puede iniciar (y por tanto threadvars no funcionará y por lo tanto la pila no funcionará correctamente).
490502
491503``` pascal
492504 type
493505 tc = class(tthread)
494506 procedure execute;override;
495507 end;
496508
497509 procedure tc.execute;
498510 begin
499511 end;
500512
501513 { programa principal }
502514 begin
503515 { Inicia el sistema de hilos }
504516 with tc.create(false) do
505517 begin
506518 waitfor;
507519 free;
508520 end;
509521 { ... aquí sigue tu código }
510522end.
511523```
512524
513525   (Después de que el sistema de hilos se inicia, el nucleo de ejecución establece la variable del sistema "IsMultiThread" a verdadero, lo que es utilizado por las rutinas FPC para realizar bloqueos aquí y allá. No se debe establecer esta variable de forma manual)
514526
515527- Si por alguna razón esto no te funciona, prueba este código en tu función de hilo externo:
516528
517529``` pascal
518530 function ExternalThread(param: Pointer): LongInt; stdcall;
519531 var
520532 tm: TThreadManager;
521533 begin
522534 GetThreadManager(tm);
523535 tm.AllocateThreadVars;
524536 InitThread(1000000); // ajustar el tamaño de pila inicial aquí
525537
526538 { hacer algo con hilos aquí ... }
527539
528540 Result:=0;
529541 end;
530542```
531543
532544### Identificando hilo externos
533545
534546   A veces incluso no sabes si tienes que tratar con hilos externos (v.g, si alguna librería C hace una retrollamada -callback-). Esto puede ayudar a analizar esto:
535547
5365481\. Pregunta al S.O. el identificador del subproceso actual al inicio de tu aplicación
537549
538550``` pascal
539551 Win32: GetCurrentThreadID();
540552 Darwin: GetThreadID();
541553 Linux: TThreadID(pthread_self);
542554```
543555
5445562\. Pregunta por el ID del hilo actual, dentro de la función del hilo y compáralo con el resultado del paso 1.
545557
546558## See also
547559
548560- [[Streaming components]]
549561- [[Manager Worker Threads System]]
550562- [[Example of multi-threaded application: array of threads/es|Ejemplo de una aplicación con múltiples hilos: Matriz de hilos]]
551563- [[Parallel procedures]]
552564
553565## Contribuciones
554566
555567- Versión en castellano (español) iskraelectrica (jldc) / julio de 2008.
556568
557569---
558570*From the Free Pascal wiki, page "Multithreaded Application Tutorial/es", as it stood on 2020-02-20 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history