Você está na página 1de 25

14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão

Multithreading e Processamento Paralelo no Delphi (PPL)


Processamento paralelo é um assunto que mexe com os profissionais. Muitos a amam, outros nem tanto, mas todos
respeitam seu potencial. A capacidade de dividir a carga de processamento em pequenas unidades que executam ao
mesmo tempo, é uma vantagem que não pode ser desprezada.

A maioria dos desenvolvedores não sabe explorar os recursos de multithreading e da computação paralela em seus
projetos. O grande problema não é a tecnologia. São as pessoas. Programar paralelamente não é apenas criar uma
thread, mas sim pensar paralelamente. Eis a grande dificuldade. Não é possível utilizar os recursos de mutithreading e da
computação paralela enquanto o desenvolvedor pensa apenas serialmente (linearmente). Isso é uma barreira, porque uma
vez que você ultrapassa esse limite, o resto é apenas tecnologia. E a tecnologia está aí para nos ajudar.

Mas o que é uma thread? É simplesmente um processo. Um programa é composto, inicialmente, por um processo único,
conhecido como main thread (thread principal). É o seu processo inicial. Entretanto, o programa não precisa ter
simplesmente um único processo, podendo criar outros processos à vontade. Ele começa com um, porque é o mínimo
possível para ele existir (caso contrário, ele não iria executar).

A dica aqui é pensarmos em threads como se fossem outros programas, desvinculados do programa principal. Ela não é
realmente isso, porque existem áreas do sistema que precisam ser compartilhadas (de outra forma, você não usaria
threads, e sim, faria outro programa) mas essa forma de pensar ajuda a estruturar o pensamento e evitar falhas. Vamos
ver mais sobre isso depois.

A grande parte dos exemplos que encontramos quando vamos trabalhar com TThread é sobre como fazer percorrer dois
TGauges. Esse exemplo é bom para mostrar como o processamento paralelo funciona, porque é possível visualmente
perceber que um dos gauges é mais rápido que o outro, o que dá a noção de que as threads estão sendo gerenciadas por
algo que está além dos nossos programas, no caso, o sistema operacional. Além disso, dá a percepção importante que
não existe uma sequencialidade na execução das threads, uma vez que provavelmente uma delas irá terminar bem antes
da outra. Todos esses conceitos são importantes, e necessários, mas eles não dão a capacidade de pensar dieferente.
Eles só dizem respeito à como a tecnologia funciona. E lembra o que é o mais importante? Saber como pensar!

Outros exemplos mostram como tornar uma tela responsível mesmo quando está executando um processamento. No
Delphi, se você utiliza VCL, verá que se você não utilizar um recurso de multithreading, a tela irá parar de responder
porque a thread principal, que é a responsável pelo processamento gráfico da VCL, estará ocupada executando outra
tarefa. Mesmo nesse caso, estamos falando apenas de tecnologia novamente.

É importante saber que não sou contra o conhecimento da tecnologia. Ele é essencial porque sem ela você não consegue
desenvolver seus sistemas. Mas dou mais importância em “como as coisas são feitas” ao invés de “do que elas são
feitas”.

Multithreading versus Processamento Paralelo


Precisamos antes de tudo definir as diferenças, para evitar qualquer mal entendido. Multithreading não é o mesmo que
processamento paralelo. Quando falamos de processamento paralelo, falamos de utilização de recursos de hardware com
múltiplos processadores e execução de código ao mesmo tempo, ou seja, de forma paralela. Multithreading diz respeito
à concorrência na execução dos processos. Um computador com um única core de processamento não pode executar
códigos de forma paralela, embora consiga executar códigos de forma concorrente. A concorrência é sobre a disputa da
utilização do CPU por diferentes processos, enquanto paralelismo é sobre a utilização de diferentes “cores” de
processamento. Contudo, como o sistema operacional manipula as threads em execução, ele pode fazer com que um
sistema multithread também seja paralelo, caso haja o recurso de hardware para isso.

Thread
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Classes.TThread

TThread é um wrapper de uma thread do Windows (ou outros sistemas operacionais, caso esteja utilizando o Firemonkey.
No caso do Windows, você pode saber mais em: https://msdn.microsoft.com/en-
us/library/windows/desktop/ms684841(v=vs.85).aspx) utilizando a própria API do sistema . Um wrapper é uma abstração,
uma transposição, uma ponte de algo que existe na API do Windows e que podemos utilizar no Delphi.

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 1/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
Utilizamos a TThread para conseguirmos executar processos concorrentes à main thread, ou seja, à nossa thread
principal. Assim, faremos com que o sistema “faça” duas coisas concorrentemente, de forma que o sistema operacional
divida o tempo de processamento do processador entre todas as threads.

Cabe ao sistema operacional determinar o paralelismo da execução da thread, conforme a carga do CPU. Dessa forma,
na utilização das threads, não necessariamente o código será executado de forma paralela, porque isso dependente de
outros fatores, que não a própria existência da thread.

Minha experiência demonstrou que o uso de threads concorrentes, mesmo que não utilizando os recursos da computação
paralela, embora não em todos os casos, ainda melhora a performance do sistema em geral.

Create

1 unit Unit2;
2
3 interface
4
5 uses
6 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, V
7 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
8
9 type
10 TMeuProcessoParalelo = class(TThread)
11 private
12 FAux: String;
13 FMemo: TMemo;
14 public
15 constructor Create(AMemo: TMemo); reintroduce;
16 procedure Execute; override;
17 procedure Sincronizar;
18 end;
19
20 type
21 TForm2 = class(TForm)
22 Memo1: TMemo;
23 Button2: TButton;
24 procedure Button1Click(Sender: TObject);
25 procedure Button2Click(Sender: TObject);
26 private
27 { Private declarations }
28 FMeuProcessoParalelo: TMeuProcessoParalelo;
29 public
30 { Public declarations }
31 end;
32
33 var
34 Form2: TForm2;
35
36 implementation
37
38 {$R *.dfm}
39
40 procedure TForm2.Button1Click(Sender: TObject);
41 begin
42
43 end;
44
45 { TMeuProcessoParalelo }
46
47 constructor TMeuProcessoParalelo.Create;
48 begin
49 inherited Create(True);
50 Self.FreeOnTerminate := True;
51
52 FAux := '';
53 FMemo := AMemo;
54 end;
55
56 procedure TMeuProcessoParalelo.Execute;
57 var
58 I: Integer;
59 begin
60 inherited;
61
62 I := 0;
63 while I < 100000 do
64 begin
65 I := I + 1;
66 Self.FAux := 'Valor de I: ' + IntToStr(I);
67 Self.Synchronize(Self.Sincronizar);
68 Self.Sleep(1);
69 end;

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 2/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
70 end;
71
72 procedure TMeuProcessoParalelo.Sincronizar;
73 begin
74 FMemo.Lines.Add(Self.FAux);
75 end;
76
77 procedure TForm2.Button2Click(Sender: TObject);
78 begin
79 Self.FMeuProcessoParalelo := TMeuProcessoParalelo.Create(Memo1);
80 Self.FMeuProcessoParalelo.Start;
81 end;
82
83 end.

Esse é um exemplo básico de uma implementação de uma thread no Delphi.

No construtor (Create) temos algumas coisas interessantes a saber:

Parâmetro CreateSuspended da TThread – O padrão para a criação de uma thread é que tenha o parâmetro
CreateSuspended como true. Isso fará com que a thread seja criada de forma suspensa, ou seja, ela não inicia seu
funcionamento imediatamente depois de sua criação. Caso seja definido para false, a thread já inicia a sua execução logo
depois de ser criada, mas nem sempre isso é interessante. Quando criamos uma thread suspensa, precisamos fazer o uso
do método Start para iniciarmos a thread (conforme exemplo acima). Caso ela não tenha sido criada suspensa, então o
uso do método Start não é necessário.
FreeOnTerminate – Isso é uma facilidade quando trabalhamos com thread. Por se tratar de processamento concorrente,
nós não temos o controle de quando o processo irá terminar. Por isso, é uma boa prática (a não ser que você tenha
alguma boa razão para não fazê-lo) sempre optar por FreeOnTerminate := True, pois isso fará com que a instância da
thread seja destruída, e a memória liberada, depois que terminar sua execução.

Execute

O método mais importante é o método Execute. Ele é o método que será chamado quando o processo concorrente entrar
em execução. A thread encerra sua execução (e seu ciclo de vida, caso tenha FreeOnTerminate = True) assim que chegar
na última linha do método Execute. Por isso, caso a thread precise permanecer viva por longo tempo, você deve criar um
laço de repetição (provavelmente o While) para que a thread não seja destruída.

Existe uma “flag” que determina quando a thread precisa ser destruída, chamada Terminated. Quando utilizamos
TThread.Terminated := True, estamos apenas sinalizando que a thread foi marcada para finalização. Ela não termina a
execução da thread abruptamente, apenas sinaliza que ela pode ser finalizada. Isso, com o uso do laço de repetição,
teríamos:

1 procedure TMeuProcessoParalelo.Execute;
2 begin
3 inherited;
4
5 while not Self.Terminated do
6 begin
7 //Alguma coisa aqui
8 end;
9 end;

Ou seja, estaríamos constantemente verificando se a thread está marcada para terminar, antes de realizarmos nossas
operações.

OnTerminate

Outro método muito importante de uma thread é o OnTerminated. Ele é executado no momento que a thread finaliza sua
execução e começa a se preparar para sua liberação.

Assim, caso queiramos que algo seja executado assim que a thread finalizar suas operações, OnTerminated deve ser
utilizado:

1 unit Unit2;
2
3 interface
4
5 uses
6 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, V
7 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
8
9 type
10 TMeuProcesso = class(TThread)
11 private

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 3/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
12 FAux: String;
13 FMemo: TMemo;
14 public
15 constructor Create(AMemo: TMemo); reintroduce;
16 procedure Execute; override;
17 procedure Sincronizar;
18 end;
19
20 type
21 TForm2 = class(TForm)
22 Memo1: TMemo;
23 Button2: TButton;
24 procedure Button1Click(Sender: TObject);
25 procedure Button2Click(Sender: TObject);
26 private
27 { Private declarations }
28 FMeuProcesso: TMeuProcesso;
29
30 procedure ExecutarDepoisThread(Sender: TObject);
31 public
32 { Public declarations }
33 end;
34
35 var
36 Form2: TForm2;
37
38 implementation
39
40 {$R *.dfm}
41
42 { TMeuProcesso}
43
44 constructor TMeuProcesso.Create;
45 begin
46 inherited Create(True);
47 Self.FreeOnTerminate := True;
48
49 FAux := '';
50 FMemo := AMemo;
51 end;
52
53 procedure TMeuProcesso.Execute;
54 var
55 I: Integer;
56 begin
57 inherited;
58
59 I := 0;
60 while I < 100 do
61 begin
62 I := I + 1;
63 Self.FAux := 'Valor de I: ' + IntToStr(I);
64 Self.Synchronize(Self.Sincronizar);
65 Self.Sleep(1);
66 end;
67 end;
68
69 procedure TMeuProcesso.Sincronizar;
70 begin
71 FMemo.Lines.Add(Self.FAux);
72 end;
73
74 procedure TForm2.Button2Click(Sender: TObject);
75 begin
76 Self.FMeuProcesso:= TMeuProcesso.Create(Memo1);
77 FMeuProcesso.OnTerminate := ExecutarDepoisThread;
78 Self.FMeuProcesso.Start;
79 end;
80
81 procedure TForm2.ExecutarDepoisThread(Sender: TObject);
82 begin
83 FMeuProcesso.Synchronize(procedure
84 begin
85 Memo1.Lines.Add('Fim da execução da thread!');
86 end);
87 end;
88
89 end.

Antes do início da thread, foi definido:

FMeuProcesso.OnTerminate := ExecutarDepoisThread;

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 4/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
A procedure ExecutarDepoisThread simplesmente chamou o método Synchronize passando um método anônimo para
exibir as informações no memo. Synch…oque?

Synchronize

O método syncronize foi utilizado nos dois exemplos àcima quando quisemos alimentar as informações no memo. Lembra
que as thread são procedimentos concorrentes e funcionam como se fossem programas diferentes? Synchronize é uma
forma de sincronizar as informações entre as threads, no caso, com a thread principal.

Toda alteração visual da VCL deve ser feita através da thread principal, a responsável por gerenciar a VCL. Assim, quando
uma thread pretende alterar qualquer aspecto visual deve fazê-lo através de algum método de sincronização. No caso
acima, utilizei o shyncronize.

Queue

Queue, assim como Synchronize, possui a capacidade de trabalhar com diferentes thread, todavia, enquanto Synchronize
é executada de forma serializada pela main thread quando existem diferentes threads chamando Syncronize ao mesmo
tempo, Queue simplesmente gera uma fila que é executada no tempo livre da main thread. E porque isso importa? Porque
a chamada dos métodos Syncronize penetra na main thread e, se não forem performáticos, travarão a main thread,
enquanto Queue só será executada no tempo livre da thread principal. Legal né? Assim, use Queue sempre que possível.

1 procedure TMeuProcesso.Execute;
2 var
3 I: Integer;
4 begin
5 inherited;
6
7 I := 0;
8 while I < 100000 do
9 begin
10 I := I + 1;
11 Self.FAux := 'Valor de I: ' + IntToStr(I);
12 Self.Queue(Self.Sincronizar);
13 Self.Sleep(1);
14 end;
15 end;

Tratamento de exceções nas threads


Qualquer exceção não tratada gera uma parada abrupta da execução do programa. Na thread, isso ocorre da mesma
forma, mas com o fato de que se a thread principal não ficar sabendo da exceção, a interrupção da thread que gerou a
exceção será tratada de forma transparente, sem nenhum aviso. Ela simplesmente irá parar de funcionar. Como exemplo,
altere o código conforme abaixo e faça um teste:

1 procedure TMeuProcessoParalelo.Execute;
2 var
3 I: Integer;
4 begin
5 inherited;
6
7 I := 0;
8 while I < 100000 do
9 begin
10 I := I + 1;
11 Self.FAux := 'Valor de I: ' + IntToStr(I);
12 Self.Queue(Self.Sincronizar);
13 Self.Sleep(1);
14
15 raise Exception.Create('Ocorreu um problema!');
16 end;
17 end;

Mas então, o que fazer?

Vamos utilizar o método OnTerminate para nos ajudar com o tratamento:

1 unit Unit2;
2
3 interface
4

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 5/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
5 uses
6 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
7 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
8
9 type
10 TMeuProcesso = class(TThread)
11 private
12 FAux: String;
13 FMemo: TMemo;
14 public
15 constructor Create(AMemo: TMemo); reintroduce;
16 procedure Execute; override;
17 procedure Sincronizar;
18 end;
19
20 type
21 TForm2 = class(TForm)
22 Memo1: TMemo;
23 Button2: TButton;
24 procedure Button1Click(Sender: TObject);
25 procedure Button2Click(Sender: TObject);
26 private
27 { Private declarations }
28 FMeuProcesso: TMeuProcesso;
29
30 procedure ExecutarDepoisThread(Sender: TObject);
31 procedure TratamentoDaExcecao(Sender: TObject);
32 public
33 { Public declarations }
34 end;
35
36 var
37 Form2: TForm2;
38
39 implementation
40
41 {$R *.dfm}
42
43 { TMeuProcesso }
44
45 constructor TMeuProcesso.Create;
46 begin
47 inherited Create(True);
48 Self.FreeOnTerminate := True;
49
50 FAux := '';
51 FMemo := AMemo;
52 end;
53
54 procedure TMeuProcesso.Execute;
55 var
56 I: Integer;
57 begin
58 inherited;
59
60 I := 0;
61 while I < 100000 do
62 begin
63 I := I + 1;
64 Self.FAux := 'Valor de I: ' + IntToStr(I);
65 Self.Queue(Self.Sincronizar);
66 Self.Sleep(1);
67
68 raise Exception.Create('Ocorreu um probema!');
69 end;
70 end;
71
72 procedure TMeuProcesso.Sincronizar;
73 begin
74 FMemo.Lines.Add(Self.FAux);
75 end;
76
77 procedure TForm2.TratamentoDaExcecao(Sender: TObject);
78 begin
79 if Sender is TThread then
80 begin
81 if Assigned(TThread(Sender).FatalException) then
82 begin
83 Memo1.Lines.Add('Ocorreu um erro: ' + Exception(TThread(Sender).FatalException
84 end;
85 end;
86 end;
87
88 procedure TForm2.Button2Click(Sender: TObject);
89 begin
90 Self.FMeuProcesso:= TMeuProcesso.Create(Memo1);

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 6/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
91 FMeuProcesso.OnTerminate := TratamentoDaExcecao;
92 Self.FMeuProcesso.Start;
93 end;
94
95 procedure TForm2.ExecutarDepoisThread(Sender: TObject);
96 begin
97 FMeuProcesso.Synchronize(procedure
98 begin
99 Memo1.Lines.Add('Fim da execução da thread!');
100 end);
101 end;
102
103 end.

Foi criado a procedure TratamentoDaExcecao para tratarmos a exceção. Ela é utilizada em:

FMeuProcesso.OnTerminate := TratamentoDaExcecao;

Assim, quando terminar a execução da thread, verificamos se houve alguma exceção simplesmente vendo se
TThread(Sender).FatalException é diferente de nil:

if Assigned(TThread(Sender).FatalException) then

Se for, é porque ocorreu alguma exceção que parou a execução da thread. Simples, não é?

Só com esse conhecimento já é possível realizar grandes coisas com as threads, mas vamos explorar ainda mais esse
recurso.

Threads sem complexidade


Todas as formas vista aqui anteriormente foram feitas com a criação de classes que herdam de TThread. Muitas vezes,
você gostaria que seu código fosse executado sem a complexidade de criação e gerenciamento de uma classe. Vamos
imaginar que você deseje que o sistema crie um log, e resolve jogar isso para um processamento concorrente, para já
liberar a thread principal para seguir seu caminho. Seu desejo é simplesmente que o log seja processado longe da main
thread.

A TThread possui o método TThread.CreateAnonymousThread. Com ele, você pode simplesmente passar o seu bloco
de código e dizer: “Executa isso para mim de forma concorrente!”. O CreateAnonymoThread internamente irá criar uma
thread, executar o bloco de código e finalizar a thread.

1 procedure TForm2.Button1Click(Sender: TObject);


2 begin
3 TThread.CreateAnonymousThread(procedure
4 begin
5 TThread.Synchronize(TThread.CurrentThread, procedure
6 begin
7 //Seu
8 ShowMe
9 end);
10 end).Start;
11 end;

Veja que aqui fizemos uso de um método anônimo.

O problema dos recursos compartilhados


Vamos imaginar uma casa com 4 irmãos. Cada irmão tem suas próprias necessidades, como estudar, trabalhar, viajar, etc,
todavia, existe um único carro de utilização compartilhada.

Há que se prever que em algum momento, um dos irmãos queira o carro para ir estudar e outro para trabalhar. Pode ainda
um terceiro irmão solicitar o carro para ir na farmácia, comprar medicamentos. O que fazer? Absolutamente nada. Apenas
um dos irmãos terá a condição de pegar o carro enquanto os outros terão que esperar, ou dar outra solução. Parece
lógico, certo?

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 7/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
Quando falamos de recursos computacionais, estamos falando da mesma coisa. Imagine que você possui um objeto
instanciado para a conexão com o banco de dados. Quando uma das thread está utilizando o objeto para trocar dados
com o banco, as outras threads precisam necessariamente esperar ele terminar. Imagine todos os irmãos dentro do
mesmo carro dirigindo-o de forma compartilhada. Quando um vira para a esquerda, o outro logo em seguida vira para a
direita. Eles só podem encontrar um poste em seu caminho. Na computação, os erros podem ser imprevisíveis.

Por isso, no logo no início, ressaltei a importância de “como” fazer as coisas, ao invés de qual tecnologia utilizar. A
tecnologia é apenas um meio para chegarmos aos resultados, mas precisamos antes saber para onde vamos e como
vamos.

O grande problema do desenvolvimento concorrente e paralelo é justamente preparar o sistema para trabalhar
adequadamente. O desesenvolvedor, analista, arquiteto, precisa imaginar o funcionamento do sistema como processos
independentes que podem estar continuamente em atividade e que utilizam recursos compartilhados.

E como trabalhamos com isso? Antes de tudo, devemos saber o que é um lock.

Lock
Literalmente traduzido como trava ou bloqueio. Um mecanismo de lock é um mecanismo de bloqueio, que gerencia
quando um recurso está disponível ou não. Existem diferentes formas de bloqueio, cada qual com suas próprias
características, mas todas são utilizadas com recursos compartilhados para evitar problemas.

TCriticalSection
Documentação:http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SyncObjs.TCriticalSection

Também conhecido como seção crítica. Uma sessão crítica é o mecanismo de lock que determina quem vai utilizar o
carro. Imagine que na casa dos 4 irmãos exista um pai que gerencie quem pode e quem não pode utilizar o carro. Esse
pai utiliza para isso um critério bem simples: quem solicitou o carro primeiro terá o acesso ao carro primeiro. Veja que ele
não tem inteligência para analisar qual a prioridade de cada filho. O que ele faz é simplesmente criar uma fila de
solicitações de utilização do carro e liberar o carro nessa mesma ordem. Assim, quando o primeiro terminar de utilizar o
carro, o segundo passará a utilizá-lo, e assim por diante.

Para que isso funcione, o primeiro filho diz: “Pai, me empresta o carro?” Como o carro está livre, o pai libera. Nesse
mesmo momento, o segundo filho diz: “Pai, me empresta o carro?” Como o carro já está sendo utilizado pelo primeiro filho,
o pai diz: “Não posso, o filho 1 está usando. Mas estou com o seu nome aqui e assim que ele devolver vou liberar para
você”. O terceiro filho diz: “Pai, me empresta o carro?” O pai novamente diz: “Não posso, o filho 1 está usando e o filho 2
já está na sua frente”.

Uma coisa que precisa ficar clara é que os filhos que solicitaram o carro mas não o tiveram logo em seguida, ficam
aguardando parados até a liberação do carro. Embora cada filho tenha sua própria atividade, quando eles precisam de um
recurso compartilhado, então precisam ser serializados, ou seja, um depois do outro, e isso faz com que suas atividades,
nesse momento, não sejam mais concorrentes. Por isso, quando precisar utilizar algum dos recursos de locking, faça de
forma que o lock dure o menor tempo possível, para minimizar a serialização dos processamentos das threads.

Analogias à parte, vamos à prática:

1 type
2 TObjetoMensagem = class
3 public
4 procedure DispararMensagem(AMsg: String);
5 end;
6
7 { TObjetoMensagem }
8
9 procedure TObjetoMensagem.DispararMensagem(AMsg: String);
10 begin
11 ShowMessage(AMsg);
12 end;

Considere o objeto acima como o nosso recurso compartilhado. Ele é um objeto simples, com a funcionalidade de disparar
uma mensagem visual.

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 8/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
1 procedure TForm2.FormCreate(Sender: TObject);
2 begin
3 FSessaoCritica := TCriticalSection.Create;
4 FObjetoMensagem := TObjetoMensagem.Create;
5 end;
6
7 procedure TForm2.FormDestroy(Sender: TObject);
8 begin
9 if Assigned(FObjetoMensagem) then FreeAndNil(FObjetoMensagem);
10 if Assigned(FSessaoCritica) then FreeAndNil(FSessaoCritica);
11 end;

Até agora apenas codificamos a criação e destruição da seção crítica e do objeto de mensagem. Assim, quando o
formulário abrir eles serão criados e quando o formulário for fechado eles serão destruídos.

1 //Primeira Thread
2 TThread.CreateAnonymousThread(procedure
3 begin
4 FSessaoCritica.Enter;
5 try
6 TThread.Sleep(5000);
7 TThread.Synchronize(TThread.CurrentThread,
8 procedure
9 begin
10 FObjetoMensagem.DispararMen
11 end);
12 finally
13 FSessaoCritica.Leave;
14 end;
15 end
16 ).Start;
17
18 //Segunda Thread
19 TThread.CreateAnonymousThread(procedure
20 begin
21 FSessaoCritica.Enter;
22 try
23 TThread.Sleep(1000);
24 TThread.Synchronize(TThread.CurrentThread,
25 procedure
26 begin
27 FObjetoMensagem.DispararMen
28 end);
29 finally
30 FSessaoCritica.Leave;
31 end;
32 end
33 ).Start;
34
35 //Terceira Thread
36 TThread.CreateAnonymousThread(procedure
37 begin
38 FSessaoCritica.Enter;
39 try
40 TThread.Sleep(3000);
41 TThread.Synchronize(TThread.CurrentThread,
42 procedure
43 begin
44 FObjetoMensagem.DispararMen
45 end);
46 finally
47 FSessaoCritica.Leave;
48 end;
49 end
50 ).Start;

O código acima cria a serialização das threads, para utilização do mesmo recurso, no caso, o objeto que dispara a
mensagem.

Todas as threads foram criadas utilizando-se o método CreateAnonymousThread. Assim, não precisei me preocupar com
a complexidade da criação das classes das threads. Outra coisa que é importante no exemplo, é que não temos como
saber quais das threads será executada primeiro. Em meus testes, o código seguiu conforme a sequência, mas isso não é
garantido, porque a segunda thread poderia executar antes da primeira, por exemplo.

O importante, indiferentemente de quem obtenha o recurso primeiro, é que a execução será serializada, e não mais
concorrente, porque existe uma competição para utilização de um recurso compartilhado.

Faça seus testes e veja o resultado.

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 9/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
Obs: Existe um texto escrito por Eric Grange a respeito de falhas no TCriticalSection e de como ele poderia serializar a
execução das threads de forma que fique pior do que se estivesse tudo sem thread. Você pode ler a respeito AQUI.

Aparentemente, o TCriticalSection, por ser um objeto muito pequeno e de alocação dinâmica, pode, em uma situação
onde existam várias instâncias diferentes da classe TCriticalSection, compartilhar o mesmo cache dentro do CPU, gerando
um conflito de cache.

Por sorte, existe uma pequena “dica” para contornar essa situação.

1 type
2 TFixedCriticalSection = class(TCriticalSection)
3 private
4 FDummy : array [0..95] of Byte;
5 end;

O que está sendo feito acima é justamente forçar o objeto a ter mais de 96 bytes, que no caso existem apenas por existir,
sem funcionalidade nenhuma para a classe em sí, mas que força o objeto a ser maior do que o “cache line” dos
processadores atuais (isso pode aumentar conforme a necessidade posteriormente). Ainda, segundo Eric, isso dá um
ganho de performance de até 7% maior ao atual, visto algumas otimizações do FastMM (projeto de um gerenciador de
memória que foi adota posteriormente pelo Delphi, mas que não segue o projeto como um todo. Sobre as configurações
de gerenciamento de memória do Delphi, veja Aqui).

TMonitor
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.TMonitor

Funciona semelhante ao TCriticalSection, mas com comportamento diferente. Você cria um objeto e passa o
monitoramento desse objeto para a classe TMonitor e TMonitor fica responsável por serializar os acessos ao objeto:

1 procedure TForm2.Button3Click(Sender: TObject);


2 begin
3 //Primeira Thread
4 TThread.CreateAnonymousThread(procedure
5 begin
6 FMonitor.Enter(FObjetoMensagem);
7 try
8 TThread.Sleep(5000);
9 TThread.Synchronize(TThread.CurrentThread,
10 procedure
11 begin
12 FObjetoMensagem.DispararMen
13 end);
14 finally
15 FMonitor.Exit(FObjetoMensagem);
16 end;
17 end
18 ).Start;
19
20 //Segunda Thread
21 TThread.CreateAnonymousThread(procedure
22 begin
23 FMonitor.Enter(FObjetoMensagem);
24 try
25 TThread.Sleep(1000);
26 TThread.Synchronize(TThread.CurrentThread,
27 procedure
28 begin
29 FObjetoMensagem.DispararMen
30 end);
31 finally
32 FMonitor.Exit(FObjetoMensagem);
33 end;
34 end
35 ).Start;
36
37 //Terceira Thread
38 TThread.CreateAnonymousThread(procedure
39 begin
40 FMonitor.Enter(FObjetoMensagem);
41 try
42 TThread.Sleep(3000);
43 TThread.Synchronize(TThread.CurrentThread,
44 procedure
45 begin

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 10/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
46 FObjetoMensagem.DispararMen
47 end);
48 finally
49 FMonitor.Exit(FObjetoMensagem);
50 end;
51 end
52 ).Start;
53 end;

O código acima já deve ser auto-explicativo à essa altura.

Como pode ver, a utilização é bem semelhante. Uma das vantagens do TMonitor é que você não precisa ter uma classe
instanciada do TCriticalSection, visto que TMonitor é um record.

Obs: Houve uma discussão no passado a respeito da diferença entre TCriticalSection e TMonitor, considerando a
performance e outros pontos. Pode acompanhar os resultados Aqui e Aqui, e faça seu próprio julgamento. Nos exemplos
aqui acima, pude notar que o código “ocorreu melhor concorrentemente” utilizando-se o TMonitor. Em cada execução,
uma das thread veio primeiro, ao contrário do TCriticalSection, que sempre executava – nos poucos testes que fiz – a
primeira thread primeiro. Isso é só um comentário, e não um estudo aprofundado do assunto, até porque não fui fundo o
suficiente para entender o porquê desse comportamento.

TInterlocked

Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SyncObjs.TInterlocked

Quando você precisa de alterações simples, como troca de valores entre variáveis – ou incrementar variáveis por exemplo
– você pode (e deve) utilizar TInterlocked. Ele é mais performático que outros métodos de lock e sem a complexidade de
ficar criando classes e gerenciando quem está com quem.

Imagine que você possua uma variável “Count” que deva ser incrementada em cada thread. Como cada thread ocorre
paralelamente, ao incrementar o valor de uma variável simplesmente da forma convencional:

1 Count := Count + 1;

poderá ser um ponto de falha, porque em cada momento que a thread incrementa a variável, ela precisa determinar o
valor da própria variável. Imagine que Count tenha valor 10. Então a primeira coisa que a thread identifica é que a variável
possui esse valor. Se no mesmo momento, outra thread alterou a variáve para 11, a primeira thread ainda “pensa” que a
variável possui o valor 10, e seta novamente o valor dela para 11 (que a outra thread já havia feito), perdendo assim o
valor do incremento. Esse é apenas um dos exemplos de problemas quando não utilizamos mecanismos de lock com
multithreading.

Utilizando TInterlocked, poderíamos:

1 TInterlocked.Increment(Count);

que fará o incremento da variável Count de forma segura. Simples não?

TInterlocked possui funcionalidades para incrementar, decrementar, adicionar, retirar, comparar, etc, etc. Vale a pena olhar
a documentação e verificar onde sua necessidade se encaixa.

TMutex (Mutuamente Exclusivo)


Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SyncObjs.TMutex

Semelhante a um semáforo binário, um mutex é um mecanismo que cria uma lista de solicitações de processos que
tentam acessar o mesmo recurso e determina apenas um que seguirá adiante na utilização do recurso. Seu
funcionamento é semelhante ao da seção crítica, mas com algumas diferenças. Mutex são tratados nominalmente a nível
de Kernel, o que garante uma disponibilidade entre aplicações, por outro lado, seções críticas são bem mais
performáticas.

A codificação de um mutex é:

1 procedure TForm2.MutexClick(Sender: TObject);

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 11/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
2 begin
3 //Primeira Thread
4 TThread.CreateAnonymousThread(procedure
5 begin
6 FMutex.Acquire;
7 try
8 TThread.Sleep(5000);
9 TThread.Synchronize(TThread.CurrentThread,
10 procedure
11 begin
12 FObjetoMensagem.DispararMen
13 end);
14 finally
15 FMutex.Release;
16 end;
17 end
18 ).Start;
19
20 //Segunda Thread
21 TThread.CreateAnonymousThread(procedure
22 begin
23 FMutex.Acquire;
24 try
25 TThread.Sleep(1000);
26 TThread.Synchronize(TThread.CurrentThread,
27 procedure
28 begin
29 FObjetoMensagem.DispararMen
30 end);
31 finally
32 FMutex.Release;
33 end;
34 end
35 ).Start;
36
37 //Terceira Thread
38 TThread.CreateAnonymousThread(procedure
39 begin
40 FMutex.Acquire;
41 try
42 TThread.Sleep(3000);
43 TThread.Synchronize(TThread.CurrentThread,
44 procedure
45 begin
46 FObjetoMensagem.DispararMen
47 end);
48 finally
49 FMutex.Release;
50 end;
51 end
52 ).Start;
53 end;

TSemaphore
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SyncObjs.TSemaphore

O TSemaphore funciona semelhante ao TMutex, mas possui um contador interno, definido em sua criação. Conforme os
acessos ao TSemaphore forem ocorrendo, eles vão sendo liberados enquanto o total de acesso não atingir o contador.
Caso atinja, o semáforo barrará o recurso compartilhado.

Veja o exemplo:

1 procedure TForm2.FormCreate(Sender: TObject);


2 begin
3 FSemaforo := TSemaphore.Create(nil, 2, 2, '');
4 end;
5
6 procedure TForm2.FormDestroy(Sender: TObject);
7 begin
8 if Assigned(FSemaforo) then FreeAndNil(FSemaforo);
9 end;
10
11 procedure TForm2.SemaphoreClick(Sender: TObject);
12 begin
13 //Primeira Thread
14 TThread.CreateAnonymousThread(procedure

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 12/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
15 begin
16 FSemaforo.Acquire;
17 try
18 TThread.Synchronize(TThread.CurrentThread,
19 procedure
20 begin
21 FObjetoMensagem.DispararMen
22 end);
23 TThread.Sleep(5000);
24 finally
25 FSemaforo.Release;
26 end;
27 end
28 ).Start;
29
30 //Segunda Thread
31 TThread.CreateAnonymousThread(procedure
32 begin
33 FSemaforo.Acquire;
34 try
35 TThread.Synchronize(TThread.CurrentThread,
36 procedure
37 begin
38 FObjetoMensagem.DispararMen
39 end);
40 TThread.Sleep(1000);
41 finally
42 FSemaforo.Release;
43 end;
44 end
45 ).Start;
46
47 //Terceira Thread
48 TThread.CreateAnonymousThread(procedure
49 begin
50 FSemaforo.Acquire;
51 try
52 TThread.Synchronize(TThread.CurrentThread,
53 procedure
54 begin
55 FObjetoMensagem.DispararMen
56 end);
57 TThread.Sleep(3000);
58 finally
59 FSemaforo.Release;
60 end;
61 end
62 ).Start;
63 end;

Veja que troquei o tempo de espera para depois de executar a ação, para que fique mais claro visualmente o
comportamento do semáforo.

Aqui nos meus testes, a primeira thread e a segunda executaram quase que juntas, enquanto a terceira teve que esperar
as duas primeiras executarem.

Quando utilizar um semáforo? Sempre que você quiser limitar um recurso a determinado número de utilizações
simultâneas. Um exemplo interessante que encontrei foi um no site Stackoverflow, onde ele fez a anologia de um
semáforo a um segurança de uma boate. A boate tem um limite de X pessoas, e o segurança bloqueia quando chega
nesse limite. Se alguma pessoa sai, o segurança libera o acesso daquela “vaga” que ficou livre.

Dentro de um ERP você poderia utilizar semáforos para limitar a quantidade de acessos simultâneos ao banco de dados,
ou à um webservice, por exemplo.

Caso o contador seja definido para 1, ele se transforma em uma semáforo binário, e seu comportamento será semelhante
ao do tipo TMutex.

Obs: Artigo sobre semáforo na EDN: https://edn.embarcadero.com/article/29908

TEvent
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SyncObjs.TEvent

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 13/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
Um evento é uma sinalização. Quando utilizado com thread, essa sinalização pode determinar o início, o término, ou outro
comportamento de uma thread. Imagine que você queira que a thread fique parada até que determinado fato aconteça,
como por exemplo, a finalização de uma outra thread. Uma forma de comunicar para essa thread que a outra já acabou o
que tinha que fazer, é utilizar os eventos. Talvez você queira saber quando todas as thread acabaram? Utilizamos eventos
para isso também. Como dito, o evento é uma forma de sinalizar para as outras threads que algo aconteceu.

1 procedure TForm2.EventoClick(Sender: TObject);


2 begin
3 FEvento.ResetEvent; //Zera o evento
4 FQtdNoEvento := 0;
5
6 Visor.Brush.Color := clRed;
7 lblVisor.Caption := 'Em execução';
8
9 Application.ProcessMessages;
10
11 //Primeira Thread
12 TThread.CreateAnonymousThread(procedure
13 begin
14 TInterLocked.Increment(FQtdNoEvento);
15
16 TThread.Sleep(5000);
17
18 //Todas as threads precisam verificar o contador
19 //e possuir o mesmo tratamento para ele
20 TInterLocked.Decrement(FQtdNoEvento);
21 if FQtdNoEvento = 0 then FEvento.SetEvent;
22 end
23 ).Start;
24
25 //Segunda Thread
26 TThread.CreateAnonymousThread(procedure
27 begin
28 TInterLocked.Increment(FQtdNoEvento);
29
30 TThread.Sleep(2000);
31
32 //Todas as threads precisam verificar o contador
33 //e possuir o mesmo tratamento para ele
34 TInterLocked.Decrement(FQtdNoEvento);
35 if FQtdNoEvento = 0 then FEvento.SetEvent;
36 end
37 ).Start;
38
39 //Terceira Thread
40 TThread.CreateAnonymousThread(procedure
41 begin
42 TInterLocked.Increment(FQtdNoEvento);
43
44 TThread.Sleep(7000);
45
46 //Todas as threads precisam verificar o contador
47 //e possuir o mesmo tratamento para ele
48 TInterLocked.Decrement(FQtdNoEvento);
49 if FQtdNoEvento = 0 then FEvento.SetEvent;
50 end
51 ).Start;
52
53 case FEvento.WaitFor(10000) of
54 wrSignaled : begin
55 Visor.Brush.Color := clGreen;
56 lblVisor.Caption := 'Livre';
57 end;
58 wrTimeout : raise Exception.Create('Tempo de timeout estourado');
59 wrAbandoned: raise Exception.Create('Objeto do evento não existe mais!');
60 wrError : raise Exception.Create('Ocorreu um erro!');
61 end;
62 end;

O que fizemos aqui em cima foi zerar o evento:

FEvento.ResetEvent;

Isso o configura para que possamos utiliza-lo. Também zeramos uma variável global:

FQtdNoEvento := 0;

Essa variável tem o objetivo de contar quantas threads estão em execução. Depois, criamos três threads, cada uma com
um tempo diferente de execução. Como primeiro passo, todas as threads incrementam a variável global, como indicativo

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 14/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
de que ela está sendo executada. Antes do final, cada thread decrementa o valor da variável, para indicar que finalizou, ou
seja, já fez tudo o que precisava. Por fim, em cada thread é testado:

if FQtdNoEvento = 0 then FEvento.SetEvent;

Assim, se todas as threads já finalizaram, FEvento recebe a informação de “disparo”. Isso libera a thread principal, que
estava com a execução parada em:

FEvento.WaitFor(10000);

O comando WaitFor trava a execução da thread enquanto o evento ainda não foi disparado (SetEvent). 10000 é o tempo
de timeout, ou seja, a thread principal irá aguardar por 10000 milesegundo (10 segundos) o retorno. Se não obtiver um
retorno em 10 segundos, irá seguir em frente. WaitFor pode retornar 4 coisas:

wrSignaled – Quando tudo ocorreu bem e o evento foi disparado (SetEvent).


wrTimeout – Quando o tempo definido já foi ultrapassado sem que SetEvento tenha sido chamado.
wrAbandoned – Quando o objeto do evento tenha sido destruído antes do tempo de timeout.
wrError -Quando algum erro ocorreu enquanto estava aguardando.

Ao invés de definir um tempo de timeout, você pode utilizar a constante INFINITE, que faz com que a thread aguarde
indefinidamente. Muito cuidado ao utilizá-la, porque caso algum problema tenha ocorrido, o programa irá travar
indefinidamente, porque pode nunca haver um retorno. Como boa prática, é recomendável a utilização de um valor para o
timeout.

TSpinlock
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SyncObjs.TSpinLock

Nos mecanismos de lock tradicionais e visto aqui anteriormente, o sistema operacional coloca as threads que “estão
aguardando” em um estado de descanso. Isso faz com que aquelas threads não consumam tempo de CPU. É uma
otimização inteligente realizada pelo sistema operacional para poupar processamento. Todavia, existem casos onde a
performance é necessária. Existe uma forma de criar o lock sem colocar as threads em estado de descanso, fazendo com
que as threads continuem em execução. Isso só é benéfico quando o tempo de duração do lock é muito curto, e quando
performance é realmente necessária, pois de outra forma, as threads ficarão consumindo tempo de processamento
desnecessariamente. Fique atento porque TSpinLock não interrompe a execução da thread, então pense bem antes de
utilizá-lo.

Basicamente a thread fica em loop aguardando o momento em que ela consegue acesso ao recurso compartilhado.

1 procedure TForm2.SpinLockClick(Sender: TObject);


2 var
3 SpinLock: TSpinLock;
4 begin
5 //Primeira Thread
6 TThread.CreateAnonymousThread(procedure
7 begin
8 SpinLock := TSpinLock.Create(True);
9 SpinLock.Enter;
10 try
11 TThread.Sleep(5000);
12 TThread.Synchronize(TThread.CurrentThread,
13 procedure
14 begin
15 FObjetoMensagem.DispararMen
16 end);
17 finally
18 SpinLock.Exit;
19 end;
20 end
21 ).Start;
22
23 //Segunda Thread
24 TThread.CreateAnonymousThread(procedure
25 begin
26 SpinLock := TSpinLock.Create(True);
27 SpinLock.Enter;
28 try
29 TThread.Sleep(1000);
30 TThread.Synchronize(TThread.CurrentThread,

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 15/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
31 procedure
32 begin
33 FObjetoMensagem.DispararMen
34 end);
35 finally
36 SpinLock.Exit;
37 end;
38 end
39 ).Start;
40
41 //Terceira Thread
42 TThread.CreateAnonymousThread(procedure
43 begin
44 SpinLock := TSpinLock.Create(True);
45 SpinLock.Enter;
46 try
47 TThread.Sleep(3000);
48 TThread.Synchronize(TThread.CurrentThread,
49 procedure
50 begin
51 FObjetoMensagem.DispararMen
52 end);
53 finally
54 SpinLock.Exit;
55 end;
56 end
57 ).Start;
58
59 end;

TSpinLock possui como parâmetro de entrada de seu construtor uma variável booleana chamada EnableThreadTracking.
Ele não permite que o lock entre duas vezes. Caso isso aconteça, quando EnableThreadTracking = True, irá ocorrer um
erro. Se definido para false, poderá haver um deadlock. Assim, recomendo que utilize como true.

Ainda é possível utilizar o método TryEnter para checar quando a entrada no lock é possível (o método Enter também faz
isso, mas ele aguarda por um tempo indeterminado – constante INFINITE – e entra quando possível). Assim, é possível
fazer um loop que aguarde enquanto não é possivel entrar e tratar o comportamento do sistema.

Quer entender mais sobre esse mecanismo?

Os links possuem exemplos que não estão em Delphi, mas qualquer interessado com um pouco de empenho conseguirá
entender:

Estrutura SpinLock
Como usar SpinLock para sincronização de baixo nível
InitializeCriticalSectionAndSpinCount function

Use com sabedoria.

TMultiReadExclusiveWriteSynchronizer
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SysUtils.TMultiReadExclusiveWriteSynchronizer

Um dos grandes problemas das threads é na utilização dos recursos compartilhados – se você leu até aqui, já deve ter
percebido isso. Por se tratar de processos concorrentes, tudo o que é compartilhado precisa ser controlado para que não
ocorram problemas. Porém, imagine um cenário onde poucos processos alteram os valores, mas muitos outros apenas
utilizam a informação desses recursos compartilhados. Quando você possui um valor e esse valor precisa ser “lidos” em
vários processos concorrentes, não há problema que todos esses processos o utilizem ao mesmo tempo, concorda? O
problema reside se algum desses processos queira alterar o valor. Mas somente utilizar o valor que já está la? Não tem
risco algum.

TMultiReadExclusiveWriteSynchronizer foi criado justamente para isso. O seu funcionamento é muito simples. Quando
uma thread deseja utilizar o recurso, ela determina a sua intenção: ler ou escrever. Se ela deseja apenas ler, o seu acesso
é liberado, desde que ninguém esteja escrevendo. Assim, você pode ter diversas threads utilizando o mesmo recurso, se
todas forem apenas ler o conteúdo, sem travar nenhuma das threads. Porém, se alguma thread utilizar
TMultiReadExclusiveWriteSynchronizer como escrita, todas as threads que tentarem o lock novamente serão travadas,
mesmo que sejam apenas para leitura, enquanto o processo de escrita não tenha terminado.

Isso é um ótimo mecanismo para agilizar aqueles processos que utilizam o recurso apenas para leitura, e quando o
processo de escrita não seja tão frequente.
edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 16/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
1 procedure TForm2.LeituraEscritaClick(Sender: TObject);
2 begin
3 //Primeira Thread - Gravação
4 TThread.CreateAnonymousThread(procedure
5 begin
6 TThread.Sleep(3000);
7 FLeituraEscrita.BeginWrite;
8 try
9 TThread.Synchronize(TThread.CurrentThread,
10 procedure
11 begin
12 Memo1.Lines.Add('Iniciou a e
13 TThread.CurrentThread.Sleep
14 Memo1.Lines.Add('Finalizou a
15 end);
16 finally
17 FLeituraEscrita.EndWrite;
18 end;
19 end
20 ).Start;
21
22 //Segunda Thread - Leitura
23 TThread.CreateAnonymousThread(procedure
24 var
25 I: Integer;
26 begin
27 for I := 1 to 10 do
28 begin
29 FLeituraEscrita.BeginRead;
30 try
31 TThread.Synchronize(TThread.CurrentThread,
32 procedure
33 begin
34 Memo1.Lines.Add('Leitura
35 TThread.CurrentThread.Slee
36 end);
37 finally
38 FLeituraEscrita.EndRead;
39 end;
40 end;
41 end
42 ).Start;
43
44 //Terceira Thread - Leitura
45 TThread.CreateAnonymousThread(procedure
46 var
47 I: Integer;
48 begin
49 for I := 1 to 10 do
50 begin
51 FLeituraEscrita.BeginRead;
52 try
53 TThread.Synchronize(TThread.CurrentThread,
54 procedure
55 begin
56 Memo1.Lines.Add('Leitura
57 TThread.CurrentThread.Slee
58 end);
59 finally
60 FLeituraEscrita.EndRead;
61 end;
62 end;
63 end
64 ).Start;
65 end;

A primeira thread foi projetada para escrita. As demais, apenas para leitura. Execute o código acima veja que enquanto
elas estão apenas lendo, elas executam concorrentemente sem problemas, entratante, quando a primeira thread pega o
lock, todas aguardam a sua liberação.

Existe ainda a interface IReadWriteSync que pode ser utilizada ao invés da classe, se você preferir.

TThread.WaitFor
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Classes.TThread.WaitFor

O recurso já foi utilizado anteriormente quando explicamos os eventos, e aqui a lógica é a mesma. A utilização do WaitFor
trava a execução de uma thread enquanto “aguarda” por algo que ocorre em outra thread. Aqui também vale lembrar que
edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 17/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
aguardar indefinidamente pode fazer com que seu programa trave, então utilize de forma que isso não ocorra.

WaitFor aguarda a execução da thread e retorna um valor inteiro, informado pela thread que está sendo aguardada,
através de SetReturnValue:

1 procedure TForm2.WaitForClick(Sender: TObject);


2 var
3 T1: TThread;
4 T2: TThread;
5 begin
6 // Retorno = 0 -> Sucesso
7 // Retorno <> 0 -> Falha
8
9 //Primeira Thread
10 T1 := TThread.CreateAnonymousThread(procedure
11 begin
12 TThread.Sleep(1000);
13 TThread.CurrentThread.SetReturnValue(0);
14 end);
15 T1.FreeOnTerminate := False;
16 T1.Start;
17
18 //Segunda Thread
19 T2 := TThread.CreateAnonymousThread(procedure
20 begin
21 TThread.Sleep(2000);
22 TThread.CurrentThread.SetReturnValue(1);
23 TThread.CurrentThread.Terminate;
24 end
25 );
26 T2.FreeOnTerminate := False;
27 T2.Start;
28
29 if T1.WaitFor = 0 then Memo1.Lines.Add('T1 = Sucesso') else Memo1.Lines.Add('T1 = Fa
30 if T2.WaitFor = 0 then Memo1.Lines.Add('T2 = Sucesso') else Memo1.Lines.Add('T2 = Fa
31
32 T1.Free;
33 T2.Free;
34 end;

A thread precisa ser manipulada de forma que você tenha o controle da destruição dela, pois se ela se autodestruir
(T1.FreeOnTerminate := False;), você não terá acesso no momento da checagem se ela retornou com algum valor (if
T1.WaitFor = 0 then).

O uso do waitfor possibilita o desenvolvimento de sistemas mais complexos onde existe uma sincronia entre os processos.
Com ele, você consegue saber exatamente quando um processo terminou e utilizar isso para dar sequência em outras
threads.

PPL – Parallel Program Language


Documentação: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Using_the_Parallel_Programming_Library

O Delphi XE7 trouxe melhorias significativas para o desenvolvimento de softwares multiprocessados e disponibilizou
recursos de paralelismo para o desenvolvimento de sistemas. Existe ainda toda uma sobrecarga de recursos com a
criação e gerenciamentos das threads. A PPL, por outro lado, é feita para utilizar os recursos de hardware
multiprocessados e dar o poder da computação paralela aos sistemas atuais.

Thread Pool

Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.TThreadPool

TThreadPool é a classe que cuida do gerenciamento automático das threads em execução. Ela diminui a sobrecarga da
criação de todas as threads individualizadas e ainda possui a inteligência de lidar eficientemente com a carga de todos os
processadores. Essa classe pode ser reescrita pelo desenvolvedor, mas somente por uma boa razão, pois a classe nativa
já da conta do recado.

Obs: Muitos desenvolvedores preferem utilizar o FastMM, que possui seu próprio pool de threads.

TTask

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 18/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.TTask

Uma tarefa é a execução de um trecho de código de forma paralela. Imagine que o software vá exportar um arquivo texto.
Você pode simplesmente delegar isso a uma tarefa e deixar sua thread principal livre para responder aos comendos do
usuário. A ideia aqui por trás é a criação de um mecanismo simples e fácil que execute paralelamenrte trechos de código,
semelhante à chamada de TThread.CreateAnonymousThread.

1 procedure TForm2.TaskClick(Sender: TObject);


2 begin
3 TTask.Run(procedure
4 var
5 I: Integer;
6 begin
7 for I := 1 to 10 do
8 begin
9 Sleep(1000);
10 TThread.Synchronize(TThread.CurrentThread,
11 procedure
12 begin
13 Memo1.Lines.Add('Linha ' + IntToStr(I));
14 end);
15 end;
16 end);
17 end;

Aqui também vale lembrar que tudo o que foi dito a respeito de como pensar sua aplicação para trabalhar com threads se
aplica também para trabalhar com tasks e qualquer outro mecanismo de paralelismo.

TFuture

Documentação:http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.TFuture

http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.IFuture

TFuture é uma task que retorna valor. Você pode pensar nessa classe como uma função que roda de forma paralela (ao
contrário da task, que pode ser entendido como uma procedure que executa de forma paralela). Ela foi implementada de
uma forma graciosa: o sistema aguarda enquanto o resultado ainda não foi processado.

Sua declaração é bem simples. Graças ao uso dos tipos genéricos (generics), você pode, no momento da criação de
TFuture, definir qual o tipo de retorno desejado:

MinhaFuncao: IFuture<String>;

O código acima declarou uma variável do tipo IFuture<string>, que pode ser entendido como uma função que irá retornar
uma string.

Para utilarmos, basta:

1 MinhaFuncao := TTask.Future<string>(function: string


2 begin
3 //Seu código aqui!
4 end);

O fato mais legal aqui é que nesse momento o método anônimo já passa a ser processado, mas ainda não utilizamos o
valor de retorno. Mas isso não importa, porque em algum momento vamos utilizá-lo, e quando isso acontecer, ele já vai
estar calculado.

A origem do nome vem justamente dessa característica, porque criamos a função para utilizarmos seu valor futuramente.
Assim, adiantamos o processamento para uso futuro.

Abaixo um exemplo completo de utilização:

1 procedure TForm2.FutureClick(Sender: TObject);


2 var
3 Funcao1: IFuture<String>;
4 Funcao2: IFuture<String>;
5 Funcao3: IFuture<String>;
6 Funcao4: IFuture<String>;
7
8 ListaFuncoes: Array[0..3] of TTask;
9 begin
10 Memo1.Lines.Clear;
11 Application.ProcessMessages;

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 19/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
12 Memo1.Lines.Add('Iniciou!');
13
14 Funcao1 := TTask.Future<String>(function: string
15 begin
16 TThread.Sleep(1000);
17 Result := 'Função 1';
18 end);
19
20 Funcao2 := TTask.Future<String>(function: string
21 begin
22 TThread.Sleep(2000);
23 Result := 'Função 2';
24 end);
25
26 Funcao3 := TTask.Future<String>(function: string
27 begin
28 TThread.Sleep(4000);
29 Result := 'Função 3';
30 end);
31
32 Funcao4 := TTask.Future<String>(function: string
33 begin
34 TThread.Sleep(3000);
35 Result := 'Função 4';
36 end);
37
38 Memo1.Lines.Add(Funcao1.Value);
39 Memo1.Lines.Add(Funcao2.Value);
40 Memo1.Lines.Add(Funcao3.Value);
41 Memo1.Lines.Add(Funcao4.Value);
42 end;

Veja agora que vou adicionar um Sleep(5000) antes de fazer a chamada do retorno. Veja que o comportamento visual
muda, porque o tempo de processamento das tarefas já foi realizado:

1 procedure TForm2.FutureClick(Sender: TObject);


2 var
3 Funcao1: IFuture<String>;
4 Funcao2: IFuture<String>;
5 Funcao3: IFuture<String>;
6 Funcao4: IFuture<String>;
7
8 ListaFuncoes: Array[0..3] of TTask;
9 begin
10 Memo1.Lines.Clear;
11 Application.ProcessMessages;
12 Memo1.Lines.Add('Iniciou!');
13
14 Funcao1 := TTask.Future<String>(function: string
15 begin
16 TThread.Sleep(1000);
17 Result := 'Função 1';
18 end);
19
20 Funcao2 := TTask.Future<String>(function: string
21 begin
22 TThread.Sleep(2000);
23 Result := 'Função 2';
24 end);
25
26 Funcao3 := TTask.Future<String>(function: string
27 begin
28 TThread.Sleep(4000);
29 Result := 'Função 3';
30 end);
31
32 Funcao4 := TTask.Future<String>(function: string
33 begin
34 TThread.Sleep(3000);
35 Result := 'Função 4';
36 end);
37 Sleep(5000);
38
39 Memo1.Lines.Add(Funcao1.Value);
40 Memo1.Lines.Add(Funcao2.Value);
41 Memo1.Lines.Add(Funcao3.Value);
42 Memo1.Lines.Add(Funcao4.Value);
43 end;

Com isso você pode perceber claramente a principal característica desse tipo de função paralela.

Você pode querer, em determinadas situações, se certificar de que todas as tarefas paralelas foram executadas antes de
realizar algum procedimento. Para isso, existe o método WaitForAll:

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 20/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
1 procedure TForm2.FutureClick(Sender: TObject);
2 var
3 Funcao1: IFuture<String>;
4 Funcao2: IFuture<String>;
5 Funcao3: IFuture<String>;
6 Funcao4: IFuture<String>;
7
8 ListaFuncoes: Array[0..3] of ITask;
9 begin
10 Memo1.Lines.Clear;
11 Application.ProcessMessages;
12 Memo1.Lines.Add('Iniciou!');
13
14 Funcao1 := TTask.Future<String>(function: string
15 begin
16 TThread.Sleep(1000);
17 Result := 'Função 1';
18 end);
19 ListaFuncoes[0] := Funcao1;
20
21 Funcao2 := TTask.Future<String>(function: string
22 begin
23 TThread.Sleep(2000);
24 Result := 'Função 2';
25 end);
26 ListaFuncoes[1] := Funcao2;
27
28 Funcao3 := TTask.Future<String>(function: string
29 begin
30 TThread.Sleep(4000);
31 Result := 'Função 3';
32 end);
33 ListaFuncoes[2] := Funcao3;
34
35 Funcao4 := TTask.Future<String>(function: string
36 begin
37 TThread.Sleep(3000);
38 Result := 'Função 4';
39 end);
40 ListaFuncoes[3] := Funcao4;
41
42 TFuture<string>.WaitForAll(ListaFuncoes);
43
44 Memo1.Lines.Add(Funcao1.Value);
45 Memo1.Lines.Add(Funcao2.Value);
46 Memo1.Lines.Add(Funcao3.Value);
47 Memo1.Lines.Add(Funcao4.Value);
48 end;

WaitForAll aguarda a execução de todas as funções paralelas para prosseguir com o processamento do restante.

Talvez você queira esperar a execução de uma tarefa apenas, seja qual delas que execute primeira. Para isso, você pode
utilizar WaitForAny, da mesma forma que utilizou WaitForAll. Saiba apenas que o método ira aguardar a execução de
qualquer uma, sendo que aquela que terminar primeiro, já libera o processamento para seguir adiante.

1 procedure TForm2.FutureClick(Sender: TObject);


2 var
3 Funcao1: IFuture<String>;
4 Funcao2: IFuture<String>;
5 Funcao3: IFuture<String>;
6 Funcao4: IFuture<String>;
7
8 ListaFuncoes: Array[0..3] of ITask;
9 begin
10 Memo1.Lines.Clear;
11 Application.ProcessMessages;
12 Memo1.Lines.Add('Iniciou!');
13
14 Funcao1 := TTask.Future<String>(function: string
15 begin
16 TThread.Sleep(1000);
17 Result := 'Função 1';
18 end);
19 ListaFuncoes[0] := Funcao1;
20
21 Funcao2 := TTask.Future<String>(function: string
22 begin
23 TThread.Sleep(2000);
24 Result := 'Função 2';
25 end);
26 ListaFuncoes[1] := Funcao2;
27
28 Funcao3 := TTask.Future<String>(function: string
29 begin

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 21/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
30 TThread.Sleep(4000);
31 Result := 'Função 3';
32 end);
33 ListaFuncoes[2] := Funcao3;
34
35 Funcao4 := TTask.Future<String>(function: string
36 begin
37 TThread.Sleep(3000);
38 Result := 'Função 4';
39 end);
40 ListaFuncoes[3] := Funcao4;
41
42 TFuture<string>.WaitForAny(ListaFuncoes);
43
44 Memo1.Lines.Add(Funcao1.Value);
45 Memo1.Lines.Add(Funcao2.Value);
46 Memo1.Lines.Add(Funcao3.Value);
47 Memo1.Lines.Add(Funcao4.Value);
48 end;

Acredito que essa seja umas das melhores melhorias já feitas com processamento paralelo dentro do Delphi, porque dá
poder ao desenvolvimento de sistemas e facilidade ao desenvolvedor.

Loop “For” paralelo

Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.TParallel.For

No lançamento do Delphi XE7, esse foi um dos recursos mais “aclamados”. O loop for paralelo é o famoso loop “for” que
todos conhecemos, mas com a possibilidade de execução paralela. Assim, cada iteração do loop é executado de forma
paralela, dando poder de processamento e desempenho.

Não pense que você pode simplesmente refatorar seu código de modo a simplesmente trocar a instrução “loop” padrão
para o “loop paralelo”. Todas as regras vistas acima sobre recursos compartilhados e problemas de paralelismo e
concorrência se aplicam ao loop “for paralelo” também, então saiba que a utilização desse mecanismo precisa ser muito
bem planejada e sua migração é complexa.

Como regra geral, tente fazer com que o código a ser executado dentro do loop tenha a menor dependência possível com
recursos compartilhados. De fato, tente não usar recursos compartilhados dentro dele, para que a execução seje 100%
independente.

Nem sempre a migração para o paralelismo é uma coisa boa. A necessidade de controle e gerenciamento da concorrência
e do paralelismo gera uma sobrecarga no sistema e, caso o design da sua aplicação não saiba aproveitar de forma
eficiente esses recursos, com certeza o sistema ficará mais lento do que se não estivesse utilizando esses recursos.

Abaixo encontra-se a implementação básica do “loop paralelo”:

1 procedure TForm2.LoopForClick(Sender: TObject);


2 begin
3 Memo1.Lines.Clear;
4 Application.ProcessMessages;
5
6 TTask.Run(procedure
7 begin
8 TParallel.&For(1, 10,
9 procedure(AIndex: Integer)
10 begin
11 TThread.Queue(TThread.CurrentThread,
12 procedure
13 begin
14 Memo1.Lines.Add(AIndex.ToString());
15 end);
16 end);
17 end);
18 end;

Temos na instrução TParallel.&For, que o código dentro de procedure(AIndex: Integer) será executado 10 vezes, indo
de 1 à 10. O loop for paralelo trabalha criando uma task para cada iteração que será executada.

Veja que fiz com ele fosse executado dentro de uma Task. Poderia não ter feito isso. O código interno dentro do loop
ainda seria executado de forma paralela, porque esse mecanismo simplesmente cria uma task para cada iteração do loop
e a executa paralelamente, mas preferi fazer dessa maneira porque o controle do loop passa a não ser mais da main
thread (thread principal) e sim da task externa, fazendo com que a tela fique responsível.

Não existe uma ordem de execução das iteração. Parece estranho, não é? Mas tenha em mente que o código será
executado paralelamente. Isso significa que você não sabe quando as iterações irão ocorrer, e qual a ordem das
edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 22/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
iterações. Pode ser que a iteração 7 ocorra antes da iteração 3. Por isso o seu código interno de execução não pode
utilizar essa ordenação como regra de negócio.

Mas e se eu precisar de algum tipo de tratamento quanto a isso?

TLoopState

Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.TParallel.TLoopState

Conforme a documentação oficial, o loop paralelo possui diversas sobrevaragas de métodos dando a possibilidade de
“encaixe” em diferentes situações. Uma dessas possibilidades é a utilização da classe TLoopState, que armazena as
informações do “estado” do loop.

TLoopstate possui os métodos “break” e “stop”, que são utilizados para a parada da execução do loop. Ainda, possui a
propriedade “stopped” para identificar quando o loop não deve mais ser executado. Lembre-se que, quando trabalhamos
com uma thread, elas são “notificadas” da parada, e não abruptamente paradas. Assim, o código interno do
processamento paralelo deve prever essas situações e se utilizar desses recursos para tratar as situações de forma
elegante.

Vamos declarar uma variável para a tarefa de forma global no relatório e uma para armazenar o resultado do loop:

1 private
2 FLoopFor: ITask;
3 FLoopResult: TParallel.TLoopResult;

Agora vamos criar uma procedure (em um TButton) para implementar o funcionamento:

1 procedure TForm2.LoopForClick(Sender: TObject);


2 begin
3 Memo1.Lines.Clear;
4 Application.ProcessMessages;
5
6 FLoopFor := TTask.Create(procedure
7 begin
8 FLoopResult := TParallel.&For(1, 100,
9 procedure(AIndex: Integer;
10 begin
11 TThread.Sleep(500);
12 if (FLoopFor.Status = TTa
13
14 if LoopState.Stopped then
15 begin
16 TThread.Queue(TThread.C
17 procedure
18 begin
19 Memo1.L
20 end);
21 end
22 else
23 begin
24 TThread.Queue(TThread.C
25 procedure
26 begin
27 Memo1.L
28 end);
29 end;
30 end);
31 end);
32 FLoopFor.Start;
33 end;

O procedimento acima apenas cria um loop com 100 iterações de forma paralela, sempre verificando o status da execução
do loop.

O comando:

TThread.Sleep(500);

existe apenas para nos dar tempo (como usuários do sistema de exemplo) para a parada do loop, para testarmos o
funcionamento.

Em outro botão temos:

1 procedure TForm2.PararLoopForClick(Sender: TObject);

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 23/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
2 begin
3 if Assigned(FLoopFor) then FLoopFor.Cancel;
4 end;

o código altera o “status” da execução para cancelado. Lembra que no código anterior verificamos o status de
cancelamento?

Em outro botão, temos:

1 procedure TForm2.VerificarParadaLoopForClick(Sender: TObject);


2 begin
3 if FLoopResult.Completed then
4 begin
5 Memo1.Lines.Add('Completou com sucesso!')
6 end
7 else
8 begin
9 Memo1.Lines.Add('Houve uma parada!')
10 end;
11 end;

Aqui apenas estamos conferindo se o loop foi interrompido ou não.

A lógica de execução desse exemplo deve ser:

. Execute o loop
. Verifique o status da parada

Com esse fluxo, você verá que todas as iterações do loop foram realizadas e o status final foi “Completou com sucesso!”.

Agora, para exemplificarmos um cenário onde ocorreu uma parada:

. Execute o loop
. Execute a parada do loop
. Verifique o status da parada

Nesse cenário, o retorno foi “Houve uma parada!” Consegue perceber também que o loop não para no momento do
cancelamento, pois ainda existem iterações que já foram disparadas e estão sendo processadas no exato momento do
cancelamento, então tenha isso em mente quando for construir seus sistemas.

Podíamos ainda utilizar “Break” e “Stop” do TLoopState para pararmos as outras iterações, como por exemplo:

1 procedure TForm2.LoopForClick(Sender: TObject);


2 begin
3 Memo1.Lines.Clear;
4 Application.ProcessMessages;
5
6 FLoopFor := TTask.Create(procedure
7 begin
8 FLoopResult := TParallel.&For(1, 100,
9 procedure(AIndex: Integer;
10 begin
11 if AIndex >= 30 then Loop
12
13 if (FLoopFor.Status = TTa
14
15 if LoopState.Stopped then
16 begin
17 TThread.Queue(TThread.C
18 procedure
19 begin
20 Memo1.L
21 end);
22 end
23 else
24 begin
25 TThread.Queue(TThread.C
26 procedure
27 begin
28 Memo1.L
29 end);
30 end;
31 end);
32 end);
33 FLoopFor.Start;
34 end;

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 24/25
14/08/2019 Multithreading e Processamento Paralelo no Delphi (PPL) | Edgar Pavão
Tanto Break como Stop param a execução das iterações dos loops, mas cada um tem sua característica singular. Para
entender a diferença, é necessário lembrarmos que a execução dos loops não possui uma ordem exata, ou seja, o “Index
90” pode executar antes do “Index 10”. Stop para qualquer nova iteração do loop, não importando qual seu index, sendo
que Break para novas iterações onde o Index dessas iterações forem maior do que o index onde ocorreu o break, ou seja,
se o break ocorre na iteração de index 50, todas as iterações onde o index for menor que 50, ainda ocorrerão, mesmo que
ainda não foram executadas. Isso te dá o poder de trabalhar melhor o fluxo de suas aplicações, não é?

TLoopResult

Documentação: http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.Threading.TParallel.TLoopResult

Simplesmente armazena as informações do resultado do loop paralelo. Sua utilização já foi exemplificada no exemplo
anterior, e utilizamos suas informações para determinar quando houveram paradas do loop.

Tratamento de exceções nas Tasks

Recomendo a seguinte leitura para o tratamento de exceções com as tarefas, do blog do Robert
Love: http://robstechcorner.blogspot.com/2015/02/tpl-ttask-exception-management.html

Problemas comuns com processamento paralelo

Race Condition

Recomendações

More Coding in Delphi de Nick Hodges.


Excelente artigo sobre processamento paralelo de Martin Harvey.

DELPHI, PROGRAMAÇÃO

C R I T I C A L S E C T I O N ,   D E L P H I ,   P P L ,   P R O G R A M A Ç Ã O PA R A L E L A ,   R A D
STUDIO, SEÇÃO CRÍTICA

edgarpavao.com/2017/08/07/multithreading-e-processamento-paralelo-no-delphi-ppl/ 25/25

Você também pode gostar