Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/pt: what changed

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

OldNewLine
11## Introdução
22
33Esta página irá tentar explicar com escrever e debugar uma aplicação multi-tarefa(multithread) com Free Pascal e Lazarus.
44
55Uma aplicação multi-tarefa(multithread) é uma que cria duas ou mais tarefas em execução que trabalham ao mesmo tempo.
66
77Se você é novo em multi-tarefa, por favor leia o parágrafo "O que você necessita para multi-tarefa ?" para descobrir, se você realmente necessita disto. Você pode se salvar de um monte de dores de cabeça.
88
99Uma das tarefas é chamada de tarefa princípal. A Tarefa Princípal é criada pelo sistema operacional, cada vez que nossa aplicação inicia. A Tarefa Principal **deve ter** somente tarefa que atualiza os componentes que faz interfaces com o usuário(senão, a aplicação pode cair).
1010
1111A principal idéia é que a aplicação pode fazer algum processamento em plano de fundo(background - numa segunda tarefa) enquando o usuário pode continuar seu trabalho (usando a tarefa principal).
1212
1313Outro uso das tarefas é somente para ter uma melhor resposta da aplicação. Se você cria uma aplicação, e quando o usuário pressiona um botão, a aplicação inicia o processamento (um grande trabalho) ... e enquanto processando, a tela para de responder, e dá ao usuário a impressão de que a aplicação está morta, que não é legal. Se o grande trabalho é executado numa segunda tarefa, a aplicação mantém-se respondendo(sempre) como se estisse inativa. Neste caso é uma boa idéia, antes iniciando a tarefa, para disabilitar os botões da janela para evitar o usuário iniciar mais que uma tarefa por trabalho.
1414
1515Outro uso, é para criar uma aplicação servidora(server application) que é abilitada para responder a vários clientes ao mesmo tempo.
1616
1717## Você necessita de multi-tarefa?
1818
1919Se você é um novato em multi-tarefa e quer somente fazer sua aplicação com melhor tempo de resposta enquanto sua aplicação processa grandes trabalhos, então multi-tarefa pode não ser, o que você está procurando.
2020
2121Aplicações multi-tarefa sempre tem pesados debugs e eles são cada vez mais complexos. E em muitos casos você não precisa de multi-tarefa. Uma simples tarefa é o suficiente.
2222
2323Multi-tarefa somente é necessário para:
2424
2525- bloquear handles, como comunicação na rede
2626- usando múltiplos processadores de uma vez
2727- algoritmos e chamadas de bibliotecas, que não podem ser separadas em pequenas partes.
2828
2929## A classe TThread
3030
3131Os exemplos a seguir podem ser encontrados no diretório examples/multithreading/.
3232
3333Para criar um aplicação multi-tarefa, a maneira mais fácil e usando a classe TThread. Esta classe permite a criação de uma thread adicional (ao lado da thread principal) de uma maneira simples. Normalmente você é obrigado a substituir apenas dois métodos: o construtor Create, e o método Execute. No construtor, você vai prepara a thread para funcionar. Você vai definir os valores iniciais das variáveis ou propriedades que você precisa. O construtor original da TThread requer um parâmetro chamado Suspended. Como você pode esperar, o ajuste Suspended = True impedirá que a thread execute automaticamente após a criação. Se Suspended = False, a thread irá executar logo após a criação. Se a thread é criada com Suspended = True, então ele será executado apenas depois que o método Resume for chamado.
3434
3535A partir da versão 2.0.1 do FPC, TThread.Create tem um parâmetro implícito para o tamanho da pilha. Agora você pode alterar o tamanho predefinido de cada pilha, para cada thread que você criar, se você precisar. Procedimentos de chamadas recursivas é um bom exemplo. Se você não especificar o parâmetro de tamanho da pilha, o tamanho padrão da pilha do OS será usado.
3636
3737Na substituição do método Execute, você escreverá o código que irá funcionar na thread.
3838
3939A classe TThread tem uma importante propriedade: Terminated : boolean; Se a thread tem um loop ( isto é típico), o loop deve ser terminado quando Terminated for True (este tem valor False porpadrão). Por cada passagem, o valor do Terminated deve ser verificado e, se for True, então o loop deve ser encerrado tão rapidamente como é apropriado, após alguma limpeza necessária. Tenha em mente que o método Terminate não faz nada por padrão: o método Execute deve explicitamente implementar o suporte para ele finalizar o seu trabalho.
4040
4141Como já explicamos anteriormente, a thread não deve interagir com componentes visuais. Atualizações para componentes visíveis deve ser feita dentro do contexto da thread principal. Para fazer isto, existe um método em TThread chamado Synchronize. Synchronize requer um método (este não leva nenhum parâmetro) como um argumento. Quando você chamar esse método através Synchronize(@MyMethod) a execução do thread será pausada, o código de MyMethod funcionará na thread principal, e a execução da thread será recomeçada então. O funcionamento exato de Synchronize depende da plataforma, mas basicamente é isto: afixa uma mensagem na fila de mensagem principal e vai dormir. Eventualmente a thread principal processa a mensagem e chama MyMethod. Desta forma MyMethod é chamado sem contexto, que não significa que seja durante um evento do mouse ou durante o evento Paint, mas depois. Após a thread principal ter executado MyMethod, desperta e processa a próxima mensagem. A thread então continua.
4242
4343Há uma outra propriedade importante de TThread: FreeOnTerminate. Se esta propriedade é True, o objeto da thread é automaticamente liberado quando a execução da thread (método Execute) terminar. Se não a aplicação precisará ser liberada manualmente.
4444
4545Exemplo:
4646
4747``` pascal
4848 
4949 Type
5050 TMyThread = class(TThread)
5151 private
5252 fStatusText : string;
5353 procedure ShowStatus;
5454 protected
5555 procedure Execute; override;
5656 public
5757 Constructor Create(CreateSuspended : boolean);
5858 end;
5959
6060 constructor TMyThread.Create(CreateSuspended : boolean);
6161 begin
6262 FreeOnTerminate := True;
6363 inherited Create(CreateSuspended);
6464 end;
6565
6666 procedure TMyThread.ShowStatus;
6767 // this method is executed by the mainthread and can therefore access all GUI elements.
6868 begin
6969 Form1.Caption := fStatusText;
7070 end;
7171
7272 procedure TMyThread.Execute;
7373 var
7474 newStatus : string;
7575 begin
7676 fStatusText := 'TMyThread Starting...';
7777 Synchronize(@Showstatus);
7878 fStatusText := 'TMyThread Running...';
7979 while (not Terminated) and ([any condition required]) do
8080 begin
8181 ...
8282 [here goes the code of the main thread loop]
8383 ...
8484 if NewStatus <> fStatusText then
8585 begin
8686 fStatusText := newStatus;
8787 Synchronize(@Showstatus);
8888 end;
8989 end;
9090 end;
9191```
9292
9393Na aplicação:
9494
9595``` pascal
9696
9797 var
9898 MyThread : TMyThread;
9999 begin
100100 MyThread := TMyThread.Create(True); // This way it doesn't start automatically
101101 ...
102102 [Here the code initialises anything required before the threads starts executing]
103103 ...
104104 MyThread.Resume;
105105 end;
106106```
107107
108108Se você quer fazer sua aplicação mais flexível, você pode criar um evento para o thread; Desta forma o seu método sincronizado não será fortemente acoplado com um formulário ou classe específico: você pode anexar os ouvintes de eventos da thread.
109109
110110Aqui está um exemplo:
111111
112112``` pascal
113113 
114114 Type
115115 TShowStatusEvent = procedure(Status: String) of Object;
116116 
117117 TMyThread = class(TThread)
118118 private
119119 fStatusText : string;
120120 FOnShowStatus: TShowStatusEvent;
121121 procedure ShowStatus;
122122 protected
123123 procedure Execute; override;
124124 public
125125 Constructor Create(CreateSuspended : boolean);
126126 property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
127127 end;
128128 
129129 constructor TMyThread.Create(CreateSuspended : boolean);
130130 begin
131131 FreeOnTerminate := True;
132132 inherited Create(CreateSuspended);
133133 end;
134134 
135135 procedure TMyThread.ShowStatus;
136136 // this method is executed by the mainthread and can therefore access all GUI elements.
137137 begin
138138 if Assigned(FOnShowStatus) then
139139 begin
140140 FOnShowStatus(fStatusText);
141141 end;
142142 end;
143143 
144144 procedure TMyThread.Execute;
145145 var
146146 newStatus : string;
147147 begin
148148 fStatusText := 'TMyThread Starting...';
149149 Synchronize(@Showstatus);
150150 fStatusText := 'TMyThread Running...';
151151 while (not Terminated) and ([any condition required]) do
152152 begin
153153 ...
154154 [here goes the code of the main thread loop]
155155 ...
156156 if NewStatus <> fStatusText then
157157 begin
158158 fStatusText := newStatus;
159159 Synchronize(@Showstatus);
160160 end;
161161 end;
162162 end;
163163```
164164
165165Na Aplicação,
166166
167167``` pascal
168168
169169 Type
170170 TForm1 = class(TForm)
171171 Button1: TButton;
172172 Label1: TLabel;
173173 procedure FormCreate(Sender: TObject);
174174 procedure FormDestroy(Sender: TObject);
175175 private
176176 { private declarations }
177177 MyThread: TMyThread;
178178 procedure ShowStatus(Status: string);
179179 public
180180 { public declarations }
181181 end;
182182 
183183 procedure TForm1.FormCreate(Sender: TObject);
184184 begin
185185 inherited;
186186 MyThread := TMyThread.Create(true);
187187 MyThread.OnShowStatus := @ShowStatus;
188188 end;
189189 
190190 procedure TForm1.FormDestroy(Sender: TObject);
191191 begin
192192 MyThread.Terminate;
193193 MyThread.Free;
194194 inherited;
195195 end;
196196 
197197 procedure TForm1.Button1Click(Sender: TObject);
198198 begin
199199 MyThread.Resume;
200200 end;
201201 
202202 procedure TForm1.ShowStatus(Status: string);
203203 begin
204204 Label1.Caption := Status;
205205 end;
206206```
207207
208208## Coisas para ter cuidados especiais
209209
210210No Windows há um possível problema quando se usa threads e você usar a opção -Ct (checagem de pilha). Por razões não tão claras a verificação de pilhas será disparada em qualquer TThread.Create, se você usar o padrão de tamanho da pilha. A única alternativa no momento é simplesmente não usar a opção -Ct. Nota-se que não causa uma exceção no segmento principal, mas no recém-criado. É como se a thread não tivesse iniciado.
211211
212212Um bom código para verificar esta e outras exceções que podem ocorrer na criação da threads é:
213213
214214``` pascal
215215 MyThread:=TThread.Create(False);
216216 if Assigned(MyThread.FatalException) then
217217 raise MyThread.FatalException;
218218```
219219
220220Esse código vai garantir que qualquer exceção que ocorra durante a criação da thread seja lançada em seu segmento principal.
221221
222222## Unidades necessárias para uma aplicação multi-tarefa
223223
224224Você não precisa de qualquer unidade especial para funcionar no Windows. Contudo com Linux, Mac OS X e FreeBSD, você vai precisar da unidade cthreads e deve ser a primeira unidade do projeto. (a unidade do programa, .lpr)! Assim, o código do aplicativo Lazaro deve ser parecido:
225225
226226``` pascal
227227 program MyMultiThreadedProgram;
228228 {$mode objfpc}{$H+}
229229 uses
230230 {$ifdef unix}
231231 cthreads,
232232 cmem, // the c memory manager is on some systems much faster for multi-threading
233233 {$endif}
234234 Interfaces, // this includes the LCL widgetset
235235 Forms
236236 { add your units here },
237237```
238238
239239Se você esquecer isso, você irá obter este erro na inicialização:
240240
241-*`This binary has no thread support compiled in.`*\
242-*`Recompile the application with a thread-driver in the program uses clause before other units using thread.`*
241+``` pascal
242+This binary has no thread support compiled in.
243+Recompile the application with a thread-driver in the program uses clause before other units using thread.
244+```
243245
244246Pacotes que utilizam multi-tarefa devem adicionar o indicador -dUseCThreads nas opções de uso personalizado. Abra o pacote, e no editor vá em Options \> Usage \> Custom e adicione -dUseCThreads. Isto irá definir este indicador para todos os projetos e pacotes usando este pacote, incluindo a IDE. A IDE e todas as novas aplicações criadas já terá o seguinte código incluído no arquivo .lpr:
245247
246248``` pascal
247249 uses
248250 {$IFDEF UNIX}{$IFDEF UseCThreads}
249251 cthreads,
250252 cmem, // the c memory manager is on some systems much faster for multi-threading
251253 {$ENDIF}{$ENDIF}
252254```
253255
254256## Suporte para Sistema de Multi Processo Simétrico (SMP)
255257
256258A boa notícia é que se sua aplicação trabalhar com suporte a multi-tarefa desta forma, já terá SMP habilitado!
257259
258260## Depuração de aplicações multi-tarefa com Lazaro
259261
260262A depuração no Lazarus não está ainda totalmente funcional.
261263
262264### Depuração de saída
263265
264266Em um aplicativo de thread simples, você pode simplesmente escrever console/terminal/ou qualquer um e a ordem das linhas é a mesma que foram escritas. Em aplicações multi-tarefa as coisas são mais complicadas. Se duas threads estão escrevendo, dizem que uma linha é escrita pela thread A antes de uma thread B, as linhas não são necessariamente escritas nessa ordem. Pode acontecer, de uma thread escrever sua saída, enquanto outra thread está escrevendo uma linha.
265267
266268A unidade LCLProc contem várias funções, para deixar cada thread escrever o seu próprio arquivo log.
267269
268270``` pascal
269271 procedure DbgOutThreadLog(const Msg: string); overload;
270272 procedure DebuglnThreadLog(const Msg: string); overload;
271273 procedure DebuglnThreadLog(Args: array of const); overload;
272274 procedure DebuglnThreadLog; overload;
273275```
274276
275277Por Exemplo: Em vez de *writeln('Algum texto ',123);* use *DebuglnThreadLog(\['Some text ',123\]);* Isto irá adicionar uma linha 'Algum texto 123' em **Log\<PID\>.txt**, onde \<PID\> e o ID do processo da thread corrente.
276278
277279É uma boa idéia remover o arquivo de log antes de cada execução:
278280
279-`rm -f Log* && ./project1`
281+```text
282+rm -f Log* && ./project1
283+```
280284
281285### Linux
282286
283287Se você tentar depurar um aplicativo multi-tarefa em Linux, você terá um grande problema: o servidor X irá travar. Não se sabe como resolver isso corretamente, mas uma solução é:
284288
285289Crie uma nova instância do X:
286290
287-`X :1 &`
291+```text
292+X :1 &
293+```
288294
289295Será aberta uma nova seção X, e quando você mudar para a nova seção X (para retornar ao que você estava trabalhando pressione CTRL+ALT+F7), você será capaz de voltar para a nova tela gráfica usando CTRL+ALT+F8 (Se essa combinação não funcionar, tente com CTRL+ALT+F2 ...isto se estiver trabalhado com Slackware)
290296
291297Então você poderia, se quiser, criar uma área de trabalho na seção X iniciada com:
292298
293-`gnome-session --display=:1 &`
299+```text
300+gnome-session --display=:1 &
301+```
294302
295303Então, no Lazaro, nos parâmetros de execução do projeto, verifique “Use display” e digite :1. Agora a aplicação será executada no segundo servidor X e depurada no primeiro. Isto foi testado com Free Pascal 2.0 e Lazarus 0.9.10 no Windows e Linux.
296304
297305------------------------------------------------------------------------
298306
299307Em vez de criar uma nova seção X, pode-se usar Xnest. Xnest é uma seção X em uma janela. Usando isto o servidor X não travará durante a depuração das threads, e é muito mais fácil de depurar sem ter que alternar entre os terminais o tempo todo.
300308
301309A linha de comando para executar Xnest é
302310
303-`Xnest :1 -ac`
311+```text
312+Xnest :1 -ac
313+```
304314
305315para criar uma seção X sobre :1, e desabilitar os controles de acesso.
306316
307317## Widgetsets
308318
309319As interfaces win32, GTK e a carbono suportam plenamente multi-tarefa. Isso significa que a classe TThreads, as seções críticas (TCriticalSection) e a sincronização (Synchronize) funcionarão.
310320
311321## Seções críticas
312322
313323Uma seção crítica é um objeto usado para certificar-se, de que alguma parte do código será executado apenas por uma thread de cada vez. Uma seção crítica precisa ser criada e inicializada antes de ser usada e deve ser liberado quando não for mais necessário.
314324
315325As seções críticas são normalmente utilizados da seguinte maneira:
316326
317327Adicione a unidade SyncObjs.
318328
319329Declare a seção globalmente para todos as threads que devem ter acesso:
320330
321331``` pascal
322332 MyCriticalSection: TRTLCriticalSection;
323333```
324334
325335Crie a seção:
326336
327337``` pascal
328338 InitializeCriticalSection(MyCriticalSection);
329339```
330340
331341Execute alguma thread, fazendo algo que deve ser exclusivo
332342
333343``` pascal
334344 EnterCriticalSection(MyCriticalSection);
335345 try
336346 // access some variables, write files, send some network packets, etc
337347 finally
338348 LeaveCriticalSection(MyCriticalSection);
339349 end;
340350```
341351
342352Após ter terminado todas as threads, libere a seção crítica:
343353
344354``` pascal
345355 DeleteCriticalSection(MyCriticalSection);
346356```
347357
348358Como alternativa, você pode usar um objeto TcriticalSection. A criação é feita pelo Inicialization, o método Enter é feita pelo EnterCriticalSection, o método Leave é feita pelo LeaveCriticalSection e a destruição do objeto é feita pelo deletion.
349359
350360Por exemplo: 5 threads incrementando um contador. Veja lazarus/examples/multithreading/criticalsectionexample1.lpi
351361
352362**CUIDADO:** Há dois conjuntos das 4 funções acima. Um Conjunto da RTL e outra da LCL. As funções da LCL estão definida nas unidade LCLIntf e LCLType. Ambos trabalham de forma parecida. Você pode usar os dois ao mesmo tempo na sua aplicação, mas você não deve usar uma função RTL com uma seção crítica LCL, e vice versa.
353363
354364## Compartilhamento de variáveis
355365
356366Se algumas threads compartilharem uma variável, que é somente leitura, então não tem nada com que se preocupar. Mas se uma ou diversas threads escrevem na variável, então você deve se certificar de que somente uma thread por vez acessará a variável.
357367
358368Por exemplo: 5 threads incrementando um contador: veja lazarus/examples/multithreading/criticalsectionexample1.lpi
359369
360370## Esperando por outra thread
361371
362372Se a thread A precisar de um resultado de outra thread B, ela deve esperar, até que B tenha terminado.
363373
364374**Importante:** A thread principal nunca deve esperar por outra thread. Ao invez disso use Synchronize (veja acima).
365375
366376Veja um exemplo: lazarus/examples/multithreading/waitforexample1.lpi
367377
368378``` pascal
369379 
370380{ TThreadA }
371381 
372382procedure TThreadA.Execute;
373383begin
374384 Form1.ThreadB:=TThreadB.Create(false);
375385 // create event
376386 WaitForB:=RTLEventCreate;
377387 while not Application.Terminated do begin
378388 // wait infinitely (until B wakes A)
379389 RtlEventWaitFor(WaitForB);
380390 writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
381391 end;
382392end;
383393 
384394{ TThreadB }
385395 
386396procedure TThreadB.Execute;
387397var
388398 i: Integer;
389399begin
390400 Counter:=0;
391401 while not Application.Terminated do begin
392402 // B: Working ...
393403 Sleep(1500);
394404 inc(Counter);
395405 // wake A
396406 RtlEventSetEvent(Form1.ThreadA.WaitForB);
397407 end;
398408end;
399409```
400410
401411Nota: RtlEventSetEvent pode ser chamado antes de RtlEventWaitFor. Então RtlEventWaitFor retornará imediatamente. Use RTLeventResetEvent para limpar um indicador.
402412
403413## Fork
404414
405415Quando replicamos uma aplicação mult-threaded, esteja ciente de que qualquer thread criada e executada ANTES de realizar a replica, NÃO será executado no processo filho. Como indicado no manual do *fork()*. Todas as threads que estavam sendo executadas antes da chamada da replica, seu estado será indefinido.
406416
407417Portanto, esteja ciente de qualquer thread inicializada antes da chamada (incluindo na seção de inicialização). Eles não irão funcionar.
408418
409419## Processos paralelos/laços
410420
411421Um caso especial de multi-tarefa e executando um procedimento único em paralelo. veja [[Parallel procedures]].
412422
413423## Computação distribuída
414424
415425Os próximos maiores passos, após programas multi-tarefa, é executar os segmentos em várias máquinas.
416426
417427- Você pode usar uma das suítes do TCP como sinapse, LNET ou Indy para comunicações. Isto lhe dá a máxima flexibilidade e é largamente utilizado para aplicações cliente / servidor com conexões rápidas.
418428
419429- Você pode usar bibliotecas de envio de mensagens, como MPICH, que são utilizados para HPC (High Performance Computing) em clusters.
420430
421431---
422432*From the Free Pascal wiki, page "Multithreaded Application Tutorial/pt", 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