Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/pt: what changed

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

Title: Multithreaded Application Tutorial/pt

OldNewLine
1+## Introdução
2+
3+Esta página irá tentar explicar com escrever e debugar uma aplicação multi-tarefa(multithread) com Free Pascal e Lazarus.
4+
5+Uma aplicação multi-tarefa(multithread) é uma que cria duas ou mais tarefas em execução que trabalham ao mesmo tempo.
6+
7+Se 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.
8+
9+Uma 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).
10+
11+A 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).
12+
13+Outro 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.
14+
15+Outro uso, é para criar uma aplicação servidora(server application) que é abilitada para responder a vários clientes ao mesmo tempo.
16+
17+## Você necessita de multi-tarefa?
18+
19+Se 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.
20+
21+Aplicaçõ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.
22+
23+Multi-tarefa somente é necessário para:
24+
25+- bloquear handles, como comunicação na rede
26+- usando múltiplos processadores de uma vez
27+- algoritmos e chamadas de bibliotecas, que não podem ser separadas em pequenas partes.
28+
29+## A classe TThread
30+
31+Os exemplos a seguir podem ser encontrados no diretório examples/multithreading/.
32+
33+Para 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.
34+
35+A 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.
36+
37+Na substituição do método Execute, você escreverá o código que irá funcionar na thread.
38+
39+A 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.
40+
41+Como 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.
42+
43+Há 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.
44+
45+Exemplo:
46+
47+``` pascal
48+ 
49+ Type
50+ TMyThread = class(TThread)
51+ private
52+ fStatusText : string;
53+ procedure ShowStatus;
54+ protected
55+ procedure Execute; override;
56+ public
57+ Constructor Create(CreateSuspended : boolean);
58+ end;
59+
60+ constructor TMyThread.Create(CreateSuspended : boolean);
61+ begin
62+ FreeOnTerminate := True;
63+ inherited Create(CreateSuspended);
64+ end;
65+
66+ procedure TMyThread.ShowStatus;
67+ // this method is executed by the mainthread and can therefore access all GUI elements.
68+ begin
69+ Form1.Caption := fStatusText;
70+ end;
71+
72+ procedure TMyThread.Execute;
73+ var
74+ newStatus : string;
75+ begin
76+ fStatusText := 'TMyThread Starting...';
77+ Synchronize(@Showstatus);
78+ fStatusText := 'TMyThread Running...';
79+ while (not Terminated) and ([any condition required]) do
80+ begin
81+ ...
82+ [here goes the code of the main thread loop]
83+ ...
84+ if NewStatus <> fStatusText then
85+ begin
86+ fStatusText := newStatus;
87+ Synchronize(@Showstatus);
88+ end;
89+ end;
90+ end;
91+```
92+
93+Na aplicação:
94+
95+``` pascal
96+
97+ var
98+ MyThread : TMyThread;
99+ begin
100+ MyThread := TMyThread.Create(True); // This way it doesn't start automatically
101+ ...
102+ [Here the code initialises anything required before the threads starts executing]
103+ ...
104+ MyThread.Resume;
105+ end;
106+```
107+
108+Se 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.
109+
110+Aqui está um exemplo:
111+
112+``` pascal
113+ 
114+ Type
115+ TShowStatusEvent = procedure(Status: String) of Object;
116+ 
117+ TMyThread = class(TThread)
118+ private
119+ fStatusText : string;
120+ FOnShowStatus: TShowStatusEvent;
121+ procedure ShowStatus;
122+ protected
123+ procedure Execute; override;
124+ public
125+ Constructor Create(CreateSuspended : boolean);
126+ property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
127+ end;
128+ 
129+ constructor TMyThread.Create(CreateSuspended : boolean);
130+ begin
131+ FreeOnTerminate := True;
132+ inherited Create(CreateSuspended);
133+ end;
134+ 
135+ procedure TMyThread.ShowStatus;
136+ // this method is executed by the mainthread and can therefore access all GUI elements.
137+ begin
138+ if Assigned(FOnShowStatus) then
139+ begin
140+ FOnShowStatus(fStatusText);
141+ end;
142+ end;
143+ 
144+ procedure TMyThread.Execute;
145+ var
146+ newStatus : string;
147+ begin
148+ fStatusText := 'TMyThread Starting...';
149+ Synchronize(@Showstatus);
150+ fStatusText := 'TMyThread Running...';
151+ while (not Terminated) and ([any condition required]) do
152+ begin
153+ ...
154+ [here goes the code of the main thread loop]
155+ ...
156+ if NewStatus <> fStatusText then
157+ begin
158+ fStatusText := newStatus;
159+ Synchronize(@Showstatus);
160+ end;
161+ end;
162+ end;
163+```
164+
165+Na Aplicação,
166+
167+``` pascal
168+
169+ Type
170+ TForm1 = class(TForm)
171+ Button1: TButton;
172+ Label1: TLabel;
173+ procedure FormCreate(Sender: TObject);
174+ procedure FormDestroy(Sender: TObject);
175+ private
176+ { private declarations }
177+ MyThread: TMyThread;
178+ procedure ShowStatus(Status: string);
179+ public
180+ { public declarations }
181+ end;
182+ 
183+ procedure TForm1.FormCreate(Sender: TObject);
184+ begin
185+ inherited;
186+ MyThread := TMyThread.Create(true);
187+ MyThread.OnShowStatus := @ShowStatus;
188+ end;
189+ 
190+ procedure TForm1.FormDestroy(Sender: TObject);
191+ begin
192+ MyThread.Terminate;
193+ MyThread.Free;
194+ inherited;
195+ end;
196+ 
197+ procedure TForm1.Button1Click(Sender: TObject);
198+ begin
199+ MyThread.Resume;
200+ end;
201+ 
202+ procedure TForm1.ShowStatus(Status: string);
203+ begin
204+ Label1.Caption := Status;
205+ end;
206+```
207+
208+## Coisas para ter cuidados especiais
209+
210+No 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.
211+
212+Um bom código para verificar esta e outras exceções que podem ocorrer na criação da threads é:
213+
214+``` pascal
215+ MyThread:=TThread.Create(False);
216+ if Assigned(MyThread.FatalException) then
217+ raise MyThread.FatalException;
218+```
219+
220+Esse código vai garantir que qualquer exceção que ocorra durante a criação da thread seja lançada em seu segmento principal.
221+
222+## Unidades necessárias para uma aplicação multi-tarefa
223+
224+Você 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:
225+
226+``` pascal
227+ program MyMultiThreadedProgram;
228+ {$mode objfpc}{$H+}
229+ uses
230+ {$ifdef unix}
231+ cthreads,
232+ cmem, // the c memory manager is on some systems much faster for multi-threading
233+ {$endif}
234+ Interfaces, // this includes the LCL widgetset
235+ Forms
236+ { add your units here },
237+```
238+
239+Se você esquecer isso, você irá obter este erro na inicialização:
240+
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.`*
243+
244+Pacotes 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:
245+
246+``` pascal
247+ uses
248+ {$IFDEF UNIX}{$IFDEF UseCThreads}
249+ cthreads,
250+ cmem, // the c memory manager is on some systems much faster for multi-threading
251+ {$ENDIF}{$ENDIF}
252+```
253+
254+## Suporte para Sistema de Multi Processo Simétrico (SMP)
255+
256+A boa notícia é que se sua aplicação trabalhar com suporte a multi-tarefa desta forma, já terá SMP habilitado!
257+
258+## Depuração de aplicações multi-tarefa com Lazaro
259+
260+A depuração no Lazarus não está ainda totalmente funcional.
261+
262+### Depuração de saída
263+
264+Em 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.
265+
266+A unidade LCLProc contem várias funções, para deixar cada thread escrever o seu próprio arquivo log.
267+
268+``` pascal
269+ procedure DbgOutThreadLog(const Msg: string); overload;
270+ procedure DebuglnThreadLog(const Msg: string); overload;
271+ procedure DebuglnThreadLog(Args: array of const); overload;
272+ procedure DebuglnThreadLog; overload;
273+```
274+
275+Por 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.
276+
277+É uma boa idéia remover o arquivo de log antes de cada execução:
278+
279+`rm -f Log* && ./project1`
280+
281+### Linux
282+
283+Se 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 é:
284+
285+Crie uma nova instância do X:
286+
287+`X :1 &`
288+
289+Será 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)
290+
291+Então você poderia, se quiser, criar uma área de trabalho na seção X iniciada com:
292+
293+`gnome-session --display=:1 &`
294+
295+Entã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.
296+
297+------------------------------------------------------------------------
298+
299+Em 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.
300+
301+A linha de comando para executar Xnest é
302+
303+`Xnest :1 -ac`
304+
305+para criar uma seção X sobre :1, e desabilitar os controles de acesso.
306+
307+## Widgetsets
308+
309+As 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.
310+
311+## Seções críticas
312+
313+Uma 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.
314+
315+As seções críticas são normalmente utilizados da seguinte maneira:
316+
317+Adicione a unidade SyncObjs.
318+
319+Declare a seção globalmente para todos as threads que devem ter acesso:
320+
321+``` pascal
322+ MyCriticalSection: TRTLCriticalSection;
323+```
324+
325+Crie a seção:
326+
327+``` pascal
328+ InitializeCriticalSection(MyCriticalSection);
329+```
330+
331+Execute alguma thread, fazendo algo que deve ser exclusivo
332+
333+``` pascal
334+ EnterCriticalSection(MyCriticalSection);
335+ try
336+ // access some variables, write files, send some network packets, etc
337+ finally
338+ LeaveCriticalSection(MyCriticalSection);
339+ end;
340+```
341+
342+Após ter terminado todas as threads, libere a seção crítica:
343+
344+``` pascal
345+ DeleteCriticalSection(MyCriticalSection);
346+```
347+
348+Como 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.
349+
350+Por exemplo: 5 threads incrementando um contador. Veja lazarus/examples/multithreading/criticalsectionexample1.lpi
351+
352+**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.
353+
354+## Compartilhamento de variáveis
355+
356+Se 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.
357+
358+Por exemplo: 5 threads incrementando um contador: veja lazarus/examples/multithreading/criticalsectionexample1.lpi
359+
360+## Esperando por outra thread
361+
362+Se a thread A precisar de um resultado de outra thread B, ela deve esperar, até que B tenha terminado.
363+
364+**Importante:** A thread principal nunca deve esperar por outra thread. Ao invez disso use Synchronize (veja acima).
365+
366+Veja um exemplo: lazarus/examples/multithreading/waitforexample1.lpi
367+
368+``` pascal
369+ 
370+{ TThreadA }
371+ 
372+procedure TThreadA.Execute;
373+begin
374+ Form1.ThreadB:=TThreadB.Create(false);
375+ // create event
376+ WaitForB:=RTLEventCreate;
377+ while not Application.Terminated do begin
378+ // wait infinitely (until B wakes A)
379+ RtlEventWaitFor(WaitForB);
380+ writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
381+ end;
382+end;
383+ 
384+{ TThreadB }
385+ 
386+procedure TThreadB.Execute;
387+var
388+ i: Integer;
389+begin
390+ Counter:=0;
391+ while not Application.Terminated do begin
392+ // B: Working ...
393+ Sleep(1500);
394+ inc(Counter);
395+ // wake A
396+ RtlEventSetEvent(Form1.ThreadA.WaitForB);
397+ end;
398+end;
399+```
400+
401+Nota: RtlEventSetEvent pode ser chamado antes de RtlEventWaitFor. Então RtlEventWaitFor retornará imediatamente. Use RTLeventResetEvent para limpar um indicador.
402+
403+## Fork
404+
405+Quando 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.
406+
407+Portanto, esteja ciente de qualquer thread inicializada antes da chamada (incluindo na seção de inicialização). Eles não irão funcionar.
408+
409+## Processos paralelos/laços
410+
411+Um caso especial de multi-tarefa e executando um procedimento único em paralelo. veja [[Parallel procedures]].
412+
413+## Computação distribuída
414+
415+Os próximos maiores passos, após programas multi-tarefa, é executar os segmentos em várias máquinas.
416+
417+- 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.
418+
419+- Você pode usar bibliotecas de envio de mensagens, como MPICH, que são utilizados para HPC (High Performance Computing) em clusters.
420+
421+---
422+*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