Bem estava dando uma estudada e me deparei com esse Tutorial, como achei bem interessante e útil, resolvi postar aqui pra vocês.
Threads
Conceitos, finalidades e aplicações
Thread é uma seqüência de programa. Em outras palavras, é um programa independente ou o próprio programa.
Por incrível que pareça, a tradução dessa palavra para o português não é muito boa: fio, linha. Não sei o motivo de seu uso no inglês.
A partir de sistemas de 32 bits, computadores e sistema operacional, podemos utilizar o recurso de threads, que consiste em disparar um programa para execução no código do próprio programa.
Em sistemas como o Windows, todo programa já pode ser considerado uma thread. Além disso, podemos executar outras partes desse programa ao mesmo tempo, como se fossem programas independentes. Isso economiza um pouco de memória. Ao invés de rodar o programa várias vezes, ocupando várias porções de memória, usamos somente uma. Além do mais, podemos liberar outras partes de execução do programa. Por exemplo, se sabemos que algo demoraria um pouco para ser executado, podemos colocar essa parte como uma thread, liberando o programa para outros usos.
Um exemplo disso é a impressão em segundo plano do MS-Word. Você manda imprimir e não precisa esperar o Word enviar todo o arquivo para a impressora para continuar a trabalhar. Claro que você espera, pois não tem mais nada para fazer além de ficar aguardando o final da impressão. Não estou falando do gerendiador de impressão, estou falando do próprio MS-Word.
Em resumo, consiste em deixar o programa fazer algo enquanto o próprio programa é liberado (fica livre para fazer outras coisas). Se você prestar atenção na forma de execução dos programas, verá que todos eles possuem uma seqüência lógica, começo meio e fim. Você só será liberado quando a execução terminar. Com threads, não há necessidade de espera de término.
Ainda falando a respeito de Windows, é bom diferenciar execução de tarefas com as telas. Estamos falando da execução de sub-rotinas. Uma tela, só por aparecer, já demonstra que o programa está livre. Se você manda executar alguma rotina, só poderá interagir com a tela novamente quando a tarefa terminar.
Em Linux, há um efeito bem interessante. O próprio sistema é uma thread. Tudo começa com o comando init. A partir dele, você pode disparar outros comandos, que serão threads de init. Programas podem disparar outros programas, que serão executados como threads de seu programa, a partir de um shell. Se você executar o comando "pstree -c" poderá entender o que estou falando. Preste atenção nos shells, que disparam seus comandos como threads. É claro que as shells rodam independentemente, uma vez que o usuário deve ser conhecido. É você quem executa e os programas que você mandar executar terão suas permições.
Em resumo, liberar o programa para fazer algo, enquanto faz outra, é a principal finalidade das threads.
Aqui, iremos utilizar o Delphi, em ambiente Windows, para mostrar nossos exemplos.
Um laço infinito
Nosso primeiro exemplo consiste em colocar um laço infinito. Você verá que não poderá fechar o programa. Somente na tela de controle de tarefas (aquela que aparece com ctrl+alt+del) ou com o debugador do Delphi você poderá encerrar essa aplicação. Portanto, muito cuidado. É claro, que mais abaixo, mostraremos formas de fechar pelo próprio programa. Se desejar, só entenda, depois, utilize os próximos exemplos, que serão melhores controlados.
Crie uma nova aplicação. Coloque uma caixa de texto (TEdit) e um botão (TButton). Em seguida, para o código do evento OnClick do botão, coloque o seguinte código:
1. "while true do" é um laço infinito por excelência. Jamais verdadeiro (true) será falso (false) e o laço irá encerrar;
2. Sleep provoca uma dormida no processo (ou thread, em se tratando de programa) em milissegundos. Isso é para o programa não ficar muito rápido no seu efeito;
3. Edit1, nesse exemplo, jamais terá o seu contúdo visto. Veja que alteramos a propriedade Text e, mesmo assim, o resultado não é visto.
Agora, a pergunta: para que utilizar um laço infinito nesse exemplo? Simples, estou tentando simular um processo lento, que demoraria bastante. Não sei qual é a sua máquina nem qual é o seu processador. Um laço infinito é um processo lento em qualquer máquina. E, não tendo fim, você poderá apreciar o efeito por um longo tempo.
Application.ProcessMessages
Esse método de Application faz com que a thread corrente (veja que, por enqutnao, só temos uma thread: a do programa principal) fique suspensa enquanto o sistema faz todas as requisições pendentes de mensagens do sistema, como refrescar componentes da tela. Desse modo, poderemos ver, no próximo exemplo, que nossa caixa de texto é alterada. Mesmo assim, o laço infinito impede de o programa ser terminado. Enquanto a thread principal não terminar suas atividades, o programa não poderá ser fechado.
Agora que podemos processar mensagens do sistema, poderemos verificar, por exemplo, o estado de uma caixa de verificação (TCheckBox). Se você acrescentar uma caixa de verificação ao formulário do programa (dê a ela o nome de CheckFim), poderá marcá-la (ou desmarcá-la) para habilitar o procedimento a terminar. Veja nossa nova modificação:
Como criar mais threads
Até agora, utilizamos somente uma thread: a do próprio programa. Vamos, a partir de agora, utilizar mais de uma thread. É bom você criar uma nova aplicação, se não for um programador experiente que consiga perceber as modificações que iremos colocar aqui. A estrutura a a mesma do início: uma caixa de texto e um botão.
Em resumo, toda thread é a execução de uma sub-rotina. Sendo assim, começa com uma sub-rotina, pode passar por outras, mas quando a primeira terminar, a thread também termina.
Com base nisso, vamos criar uma função para ser o ponto de início de nossa nova thread.
1. veja que voltamos ao nosso laço infinito real. Não teremos mais ponto de saída, como marcar uma caixa de verificação;
2. essa sub-rotina não pertence ao nosso formulário. Você deve defini-la em alguma área após a palavra implementation. Por isso, o uso de rmPrincipal.EditC.Text := , para podermos utilizar nossa caixa de texto;
3. observe o cabeçalho da sub-rotina. Na verdade, isso é um padrão. Passa-se um ponteiro para os parâmetros. Nunca usei parâmetros de sub-rotinas de threas; prefiro montar uma sub-rotina como a acima e a partir dela acionar outras sub-rotinas, passando os devidos parâmetros. O retorno também pode ser um ponteiro.
Após a definição da sub-rotina que será nosso ponto de entrada para a thread, vamos colocar o seguinte código no evento OnClick de nosso botão:
1. duas variáveis são declaradas aqui somente para sabermos qual é o identificador do processo gerado pelo Windows. Se desejar estudar mais, poderá ver que Application e todos os descendentes de TWinControl, além de objetos de algumas outras classes, possuem uma propriedade chamada Handle;
2. você pode considerar DWord (que pode armazenar um ponteiro) como um ponteiro. Um Handle, também;
3. se CreateThread conseguir criar a thread (processo), retornará um valor, diferente de zero, identificando o processo. Todos os processos, no Windows, possuem um identiricador único. No Linux, também (pid). Desta forma, poderemos colocar alguma mensagem de erro, por exemplo. Isso pode ocorrer por falta de recursos do sistema operacional, por exemplo.
A função CreateThread
ChreateThread possui 6 (seis) parâmetros, que serão explicados a seguir.
1. Atributos de segurança. Coloque sempre como nil. Em Windows NT ele é utilizado mesmo como nulo, assumindo um default, para evitar problemas. No 9x, ele é ignorado quando nulo.
2. Tamanho inicial, em bytes, da pilha. Se zero, é o mesmo tamanho da thread primária. Se necessário, no decorrer do processo, o tamanho da pilha pode crescer.
3. Ponto inicial da thread. Como já foi falado, é uma sub-rotina. Observe o uso do operador @ (arroba) para que seja passado o ponteiro (endereço) da sub-rotina Infinito.
4. Parâmetros para a sub-rotina. Como não iremos trabalhar com parâmetros para a sub-rotina da thread, será nil. Existe uma ténica para passar parâmetros para sub-rotinas que serão pontos iniciais de threads.
5. Bandeira de criação. Coloque sempre 0 (zero) para que a thread inicie imediatamente. Senão, será colocado em estado de suspensão e somente com um ResumeThread será iniciada.
6. Identificador da thread. Não há muito o que falar desse parâmetro.
Veja que, agora, poderemos fechar nossa aplicação, pois a thread principal está livre, pronta até para disparar outras threads. Você, inclusive, se apertar o botão mais de uma vez, estará criando mais de uma thread. Fechar a thread primária corresponde a fechar a aplicação. Conseqüentemente, encerrará qualquer thread ativa.
Sincronizando threads
As threads podem ser interrompidas pelo sistema operacional (processos concorrentes; o sistema operacional disponibiliza e retira o processador do processo; aplicativos 32 bits).
De fato, elas são tratadas como programas independentes que rodam no mesmo código. Pode, então, ocorrer de uma therad ser interrompida no exato momento em que outra thread altera dados. Desse modo, é bom ter cuidado com variáveis globais.
Seções críticas
Vejamos o seguinte. Uma variável global. Supondo que uma função, disparada duas vezes (threads), utilize uma variável global de nome K, inteira, cujo valor, quando par, será mostrado em uma caixa de texto (TEdit), e, quando ímpar, será mostrada em outra caixa de texto.
Vejamos o código seguinte:
A resposta é simples. Vamos ao análise do código.
1. O código consiste de um laço. Esse laço não é infinito, mas é um pouco demorado.
2. K, variável global, é incrementada em um.
3. É sorteado um tempo aleatório para o retardo da nossa thread. Se você prestou bem atenção, utilizamos retardos para simular processos demorados. Aqui, o tempo aleatório fará com que uma thread possa ser executada mais de uma vez enquanto a outra aguarda.
4. Se K contiver um valor par, aguardamos um pouco e colocamos seu valor em EditPar. Caso contrário, o valor é ímpar e colocamos seu valor em EditImpar.
Nesse último passo é que está o problema. Veja bem, testamos o valor, se par ou ímpar, e aguardamos um pouco. Nesse meio tempo da espera, a outra thread pode alterar o valor de K. E, então, provocar um "furo" em nosso programa.
Quero frisar que Sleep causa a suspensão da thread corrente. Conseqüentemente, e com certeza, a thread perdará o processador. E não o terá de volta enquanto o tempo estabelecido, e que é mínimo, não esgotar.
Você poderá disparar duas threads desse código acrescentando um botão e utilizando o seguinte código:
Contornando seções críticas
Para contornarmos esse problema, utilizaremos uma técnica de bandeiras (flags). Podemos utilizar qualquer recurso, mas o princípio será o mesmo: uma bandeira.
Criamos uma bandeira. Toda vez desejamos fazer algo em uma seção crítica, tentamos "roubar" (ou mudar) a bandeira, para que esta acuse que a seção (ou o recurso) está ocupado. Devolvemos a bandeira quando não precisarmos mais do recurso. Se você estudar sistema operacional (a nível de entender o funcionamento), irá encontrar a chamada técnica do iglu (casa de esquimó), para processos concorrentes.
No Windows, poderemos utilizar um recurso chamado de Mutex (de MUTually EXclusive), que será nossa bandeira. Um mutex nada mais é do que "um objeto" (na verdade, é um ponteiro ou manipulador) que nos será muito útil. Você pode comparar a uma fechadura de banheiro daquelas que fecham e mostram a mensagem "ocupado" do lado de fora, ou "livre" quando aberta.
Criamos mutex's com a função CreateMutex. Destruímos com CloseHandle. Informamos que vamos modificar a bandeira com WaitForSingleObject. Devolvemos com ReleaseMutex.
É bom criar o mutex logo no início de nosso programa, ou seja, quando criamos o formulário principal. Deveremos destruílo quando não mais precisármos, ou seja, quando fecharmos o formulário principal. Portanto, nosso código ficará assim:
O código de nossa função, modificado, ficará assim:
1. Antes de utilizármos a variável K dentro de nosso laço, tentamos verificar se nossa thread é a única a utilizar Bandeira. Em caso positivo, o processo continua. Senão, o processo aguarda o tempo colocado no segundo parâmetro. Veja que colocamos infinito. Então, realmente o processo só continua se formos os únicos a utilizar a bandeira. Deveremos testar o valor de retorno de WaitForSingleObject se não desejamos esperar tanto tempo assim, pois algum processo demoraro poderá ter tomado o recurso.
2. Fazemos nossa thread trabalhar.
3. Depois de terminar, liberamos a bandeira. Veja que isso é um passo muito importante, senão, podemos observar o que acontece quando alguém toma conta do banheiro.
Veja que, inclusive, houve um sincronismo quanto ao tempo da threads. Antes, além da bagunça, algum valor poderia ser pulado.
Outro ponto a ser observado é que o valor ímpar de saída será 21. Devemos tomar conta disso, também, mas, aqui, não entrará em questão. E a explicação para esse fato é simples. Quando K é 19, a thread poderá entrar. Nesse meio tempo, a outra thread poderá adicionar, chegando a 20, enquanto esperamos a liberação de Bandeira. Então, adicionamos K. Veja que a soma é interna ao laço.
Uma outra questão é que se você acionar o botão enquanto as threads trabalham, outras threads serão criadas. Mas, a thread principal não testa isso, colocando, diretamente, o valor de K para 1. Isso pode provocar um furo na função quando esta for colocar um número par. Veja que o número 1 jamais será impresso em nenhum caso, pois já começamos a incrementar no início do laço. Mesmo assim, o número 1 poderá ser impresso. E o pior é que poderá ser impresso no EditPar.
Para contornar isso, poderemos testar o mutex logo no método clique de nosso botão. Ficará assim:
Conclusões finais
* Aqui, vimos thread para uma única função. Você, em seus programas, poderá utilizar quantas funções achar necessárias para começar suas threads. Lembre-se que threads são funções. Funções podem chamar outras e até serem recursivas. Mas, somente quando a função primeira encerrar, sua thread terá encerrado.
* Você poderá utilizar mutex para qualquer recurso, inclusive em funções distintas. Veja que é uma segurança de que uma thread não irá bagunçar com outra thread.
* Quando usamos um mutex, todas as threads deverão utilizá-lo. Veja que é uma técnica de programação. Portanto, o programador tem que preocupar-se com seus recursos. Se instruímos nossas threads a verificar a bandeira antes de continuar, garantimos que nenhuma outra thread estará usando, e nem virá a usar, o recursos que desejamos enquanto nossa thread o utiliza.
* Não há vínculo algum entre o objeto mutex e o seu código. É só uma simples bandeira que você utiliza para controlar a seqüência e a utilização de recursos
Autor: Jossérgio
Threads
Conceitos, finalidades e aplicações
Thread é uma seqüência de programa. Em outras palavras, é um programa independente ou o próprio programa.
Por incrível que pareça, a tradução dessa palavra para o português não é muito boa: fio, linha. Não sei o motivo de seu uso no inglês.
A partir de sistemas de 32 bits, computadores e sistema operacional, podemos utilizar o recurso de threads, que consiste em disparar um programa para execução no código do próprio programa.
Em sistemas como o Windows, todo programa já pode ser considerado uma thread. Além disso, podemos executar outras partes desse programa ao mesmo tempo, como se fossem programas independentes. Isso economiza um pouco de memória. Ao invés de rodar o programa várias vezes, ocupando várias porções de memória, usamos somente uma. Além do mais, podemos liberar outras partes de execução do programa. Por exemplo, se sabemos que algo demoraria um pouco para ser executado, podemos colocar essa parte como uma thread, liberando o programa para outros usos.
Um exemplo disso é a impressão em segundo plano do MS-Word. Você manda imprimir e não precisa esperar o Word enviar todo o arquivo para a impressora para continuar a trabalhar. Claro que você espera, pois não tem mais nada para fazer além de ficar aguardando o final da impressão. Não estou falando do gerendiador de impressão, estou falando do próprio MS-Word.
Em resumo, consiste em deixar o programa fazer algo enquanto o próprio programa é liberado (fica livre para fazer outras coisas). Se você prestar atenção na forma de execução dos programas, verá que todos eles possuem uma seqüência lógica, começo meio e fim. Você só será liberado quando a execução terminar. Com threads, não há necessidade de espera de término.
Ainda falando a respeito de Windows, é bom diferenciar execução de tarefas com as telas. Estamos falando da execução de sub-rotinas. Uma tela, só por aparecer, já demonstra que o programa está livre. Se você manda executar alguma rotina, só poderá interagir com a tela novamente quando a tarefa terminar.
Em Linux, há um efeito bem interessante. O próprio sistema é uma thread. Tudo começa com o comando init. A partir dele, você pode disparar outros comandos, que serão threads de init. Programas podem disparar outros programas, que serão executados como threads de seu programa, a partir de um shell. Se você executar o comando "pstree -c" poderá entender o que estou falando. Preste atenção nos shells, que disparam seus comandos como threads. É claro que as shells rodam independentemente, uma vez que o usuário deve ser conhecido. É você quem executa e os programas que você mandar executar terão suas permições.
Em resumo, liberar o programa para fazer algo, enquanto faz outra, é a principal finalidade das threads.
Aqui, iremos utilizar o Delphi, em ambiente Windows, para mostrar nossos exemplos.
Um laço infinito
Nosso primeiro exemplo consiste em colocar um laço infinito. Você verá que não poderá fechar o programa. Somente na tela de controle de tarefas (aquela que aparece com ctrl+alt+del) ou com o debugador do Delphi você poderá encerrar essa aplicação. Portanto, muito cuidado. É claro, que mais abaixo, mostraremos formas de fechar pelo próprio programa. Se desejar, só entenda, depois, utilize os próximos exemplos, que serão melhores controlados.
Crie uma nova aplicação. Coloque uma caixa de texto (TEdit) e um botão (TButton). Em seguida, para o código do evento OnClick do botão, coloque o seguinte código:
- Código:
procedure TForm1.Button1Click (Sender : TObject);
var
C : Integer;
begin
C := 0;
while true do
begin
Sleep (100);
inc (C);
Edit1.Text := IntToStr (C);
end;
end;
1. "while true do" é um laço infinito por excelência. Jamais verdadeiro (true) será falso (false) e o laço irá encerrar;
2. Sleep provoca uma dormida no processo (ou thread, em se tratando de programa) em milissegundos. Isso é para o programa não ficar muito rápido no seu efeito;
3. Edit1, nesse exemplo, jamais terá o seu contúdo visto. Veja que alteramos a propriedade Text e, mesmo assim, o resultado não é visto.
Agora, a pergunta: para que utilizar um laço infinito nesse exemplo? Simples, estou tentando simular um processo lento, que demoraria bastante. Não sei qual é a sua máquina nem qual é o seu processador. Um laço infinito é um processo lento em qualquer máquina. E, não tendo fim, você poderá apreciar o efeito por um longo tempo.
Application.ProcessMessages
Esse método de Application faz com que a thread corrente (veja que, por enqutnao, só temos uma thread: a do programa principal) fique suspensa enquanto o sistema faz todas as requisições pendentes de mensagens do sistema, como refrescar componentes da tela. Desse modo, poderemos ver, no próximo exemplo, que nossa caixa de texto é alterada. Mesmo assim, o laço infinito impede de o programa ser terminado. Enquanto a thread principal não terminar suas atividades, o programa não poderá ser fechado.
- Código:
procedure TForm1.Button1Click(Sender: TObject);
var
C : Integer;
begin
C := 0;
while true do
begin
Sleep (100);
C := C + 1;
Edit1.Text := IntToStr (C);
Application.ProcessMessages;
end;
end;
Agora que podemos processar mensagens do sistema, poderemos verificar, por exemplo, o estado de uma caixa de verificação (TCheckBox). Se você acrescentar uma caixa de verificação ao formulário do programa (dê a ela o nome de CheckFim), poderá marcá-la (ou desmarcá-la) para habilitar o procedimento a terminar. Veja nossa nova modificação:
- Código:
procedure TForm1.Button1Click(Sender: TObject);
var
C : Integer;
begin
C := 0;
while not CheckFim.Checked do
begin
Sleep (100);
C := C + 1;
Edit1.Text := IntToStr (C);
Application.ProcessMessages;
end;
end;
Como criar mais threads
Até agora, utilizamos somente uma thread: a do próprio programa. Vamos, a partir de agora, utilizar mais de uma thread. É bom você criar uma nova aplicação, se não for um programador experiente que consiga perceber as modificações que iremos colocar aqui. A estrutura a a mesma do início: uma caixa de texto e um botão.
Em resumo, toda thread é a execução de uma sub-rotina. Sendo assim, começa com uma sub-rotina, pode passar por outras, mas quando a primeira terminar, a thread também termina.
Com base nisso, vamos criar uma função para ser o ponto de início de nossa nova thread.
- Código:
function Infinito (P : Pointer) : Longint;
var
C : Integer;
begin
C := 0;
while True do
begin
Sleep (100);
Form1.Edit1.Text := IntToStr (C);
inc (C);
end;
end;
1. veja que voltamos ao nosso laço infinito real. Não teremos mais ponto de saída, como marcar uma caixa de verificação;
2. essa sub-rotina não pertence ao nosso formulário. Você deve defini-la em alguma área após a palavra implementation. Por isso, o uso de rmPrincipal.EditC.Text := , para podermos utilizar nossa caixa de texto;
3. observe o cabeçalho da sub-rotina. Na verdade, isso é um padrão. Passa-se um ponteiro para os parâmetros. Nunca usei parâmetros de sub-rotinas de threas; prefiro montar uma sub-rotina como a acima e a partir dela acionar outras sub-rotinas, passando os devidos parâmetros. O retorno também pode ser um ponteiro.
Após a definição da sub-rotina que será nosso ponto de entrada para a thread, vamos colocar o seguinte código no evento OnClick de nosso botão:
- Código:
procedure TForm1.Button1Click(Sender: TObject);
var
hThreadID : THandle;
ThreadID : DWord;
begin
hThreadID := CreateThread (nil, 0, @Infinito, nil, 0, ThreadID);
if hThreadID = 0 then
{ A função não foi inicializada }
end;
1. duas variáveis são declaradas aqui somente para sabermos qual é o identificador do processo gerado pelo Windows. Se desejar estudar mais, poderá ver que Application e todos os descendentes de TWinControl, além de objetos de algumas outras classes, possuem uma propriedade chamada Handle;
2. você pode considerar DWord (que pode armazenar um ponteiro) como um ponteiro. Um Handle, também;
3. se CreateThread conseguir criar a thread (processo), retornará um valor, diferente de zero, identificando o processo. Todos os processos, no Windows, possuem um identiricador único. No Linux, também (pid). Desta forma, poderemos colocar alguma mensagem de erro, por exemplo. Isso pode ocorrer por falta de recursos do sistema operacional, por exemplo.
A função CreateThread
ChreateThread possui 6 (seis) parâmetros, que serão explicados a seguir.
1. Atributos de segurança. Coloque sempre como nil. Em Windows NT ele é utilizado mesmo como nulo, assumindo um default, para evitar problemas. No 9x, ele é ignorado quando nulo.
2. Tamanho inicial, em bytes, da pilha. Se zero, é o mesmo tamanho da thread primária. Se necessário, no decorrer do processo, o tamanho da pilha pode crescer.
3. Ponto inicial da thread. Como já foi falado, é uma sub-rotina. Observe o uso do operador @ (arroba) para que seja passado o ponteiro (endereço) da sub-rotina Infinito.
4. Parâmetros para a sub-rotina. Como não iremos trabalhar com parâmetros para a sub-rotina da thread, será nil. Existe uma ténica para passar parâmetros para sub-rotinas que serão pontos iniciais de threads.
5. Bandeira de criação. Coloque sempre 0 (zero) para que a thread inicie imediatamente. Senão, será colocado em estado de suspensão e somente com um ResumeThread será iniciada.
6. Identificador da thread. Não há muito o que falar desse parâmetro.
Veja que, agora, poderemos fechar nossa aplicação, pois a thread principal está livre, pronta até para disparar outras threads. Você, inclusive, se apertar o botão mais de uma vez, estará criando mais de uma thread. Fechar a thread primária corresponde a fechar a aplicação. Conseqüentemente, encerrará qualquer thread ativa.
Sincronizando threads
As threads podem ser interrompidas pelo sistema operacional (processos concorrentes; o sistema operacional disponibiliza e retira o processador do processo; aplicativos 32 bits).
De fato, elas são tratadas como programas independentes que rodam no mesmo código. Pode, então, ocorrer de uma therad ser interrompida no exato momento em que outra thread altera dados. Desse modo, é bom ter cuidado com variáveis globais.
Seções críticas
Vejamos o seguinte. Uma variável global. Supondo que uma função, disparada duas vezes (threads), utilize uma variável global de nome K, inteira, cujo valor, quando par, será mostrado em uma caixa de texto (TEdit), e, quando ímpar, será mostrada em outra caixa de texto.
Vejamos o código seguinte:
- Código:
function TestaSecaoCritica (P : Pointer): Longint;
begin
while K < 2000 do
Inc (K);
tempo := random (1800) + 200;
if (K mod 2) = 0 then {é par}
begin
Sleep (tempo);
Form1.EditPar.Text := IntToStr (K);
end
else {é ímpar}
begin
sleep (tempo);
Form1.EditImpar.Text := IntToStr (K);
end;
end;
end;
A resposta é simples. Vamos ao análise do código.
1. O código consiste de um laço. Esse laço não é infinito, mas é um pouco demorado.
2. K, variável global, é incrementada em um.
3. É sorteado um tempo aleatório para o retardo da nossa thread. Se você prestou bem atenção, utilizamos retardos para simular processos demorados. Aqui, o tempo aleatório fará com que uma thread possa ser executada mais de uma vez enquanto a outra aguarda.
4. Se K contiver um valor par, aguardamos um pouco e colocamos seu valor em EditPar. Caso contrário, o valor é ímpar e colocamos seu valor em EditImpar.
Nesse último passo é que está o problema. Veja bem, testamos o valor, se par ou ímpar, e aguardamos um pouco. Nesse meio tempo da espera, a outra thread pode alterar o valor de K. E, então, provocar um "furo" em nosso programa.
Quero frisar que Sleep causa a suspensão da thread corrente. Conseqüentemente, e com certeza, a thread perdará o processador. E não o terá de volta enquanto o tempo estabelecido, e que é mínimo, não esgotar.
Você poderá disparar duas threads desse código acrescentando um botão e utilizando o seguinte código:
- Código:
procedure TForm1.Button1Click(Sender: TObject);
var
hThreadID : THandle;
ThreadID : DWord;
begin
K := 1;
hThreadID := CreateThread (nil, 0, @TestaSecaoCritica, nil, 0, ThreadID);
Sleep (500);
hThreadID := CreateThread (nil, 0, @TestaSecaoCritica, nil, 0, ThreadID);
end;
Contornando seções críticas
Para contornarmos esse problema, utilizaremos uma técnica de bandeiras (flags). Podemos utilizar qualquer recurso, mas o princípio será o mesmo: uma bandeira.
Criamos uma bandeira. Toda vez desejamos fazer algo em uma seção crítica, tentamos "roubar" (ou mudar) a bandeira, para que esta acuse que a seção (ou o recurso) está ocupado. Devolvemos a bandeira quando não precisarmos mais do recurso. Se você estudar sistema operacional (a nível de entender o funcionamento), irá encontrar a chamada técnica do iglu (casa de esquimó), para processos concorrentes.
No Windows, poderemos utilizar um recurso chamado de Mutex (de MUTually EXclusive), que será nossa bandeira. Um mutex nada mais é do que "um objeto" (na verdade, é um ponteiro ou manipulador) que nos será muito útil. Você pode comparar a uma fechadura de banheiro daquelas que fecham e mostram a mensagem "ocupado" do lado de fora, ou "livre" quando aberta.
Criamos mutex's com a função CreateMutex. Destruímos com CloseHandle. Informamos que vamos modificar a bandeira com WaitForSingleObject. Devolvemos com ReleaseMutex.
É bom criar o mutex logo no início de nosso programa, ou seja, quando criamos o formulário principal. Deveremos destruílo quando não mais precisármos, ou seja, quando fecharmos o formulário principal. Portanto, nosso código ficará assim:
- Código:
procedure TForm1.FormCreate(Sender: TObject);
begin
Bandeira := CreateMutex (nil, false, nil);
end;
- Código:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
CloseHandle (Bandeira);
end;
O código de nossa função, modificado, ficará assim:
- Código:
function TestaSecaoCritica (P : Pointer) : Longint;
var
tempo : Integer;
begin
while K < 20 do
begin
WaitForSingleObject (Bandeira, INFINITE);
Inc (K);
tempo := random (1800) + 200;
if (k mod 2) = 0 then {é par}
begin
Sleep (tempo);
Form1.EditPar.Text := IntToStr (K);
end
else {é ímpar}
begin
sleep (tempo);
Form1.EditImpar.Text := IntToStr (K);
end;
ReleaseMutex (Bandeira);
end;
end;
1. Antes de utilizármos a variável K dentro de nosso laço, tentamos verificar se nossa thread é a única a utilizar Bandeira. Em caso positivo, o processo continua. Senão, o processo aguarda o tempo colocado no segundo parâmetro. Veja que colocamos infinito. Então, realmente o processo só continua se formos os únicos a utilizar a bandeira. Deveremos testar o valor de retorno de WaitForSingleObject se não desejamos esperar tanto tempo assim, pois algum processo demoraro poderá ter tomado o recurso.
2. Fazemos nossa thread trabalhar.
3. Depois de terminar, liberamos a bandeira. Veja que isso é um passo muito importante, senão, podemos observar o que acontece quando alguém toma conta do banheiro.
Veja que, inclusive, houve um sincronismo quanto ao tempo da threads. Antes, além da bagunça, algum valor poderia ser pulado.
Outro ponto a ser observado é que o valor ímpar de saída será 21. Devemos tomar conta disso, também, mas, aqui, não entrará em questão. E a explicação para esse fato é simples. Quando K é 19, a thread poderá entrar. Nesse meio tempo, a outra thread poderá adicionar, chegando a 20, enquanto esperamos a liberação de Bandeira. Então, adicionamos K. Veja que a soma é interna ao laço.
Uma outra questão é que se você acionar o botão enquanto as threads trabalham, outras threads serão criadas. Mas, a thread principal não testa isso, colocando, diretamente, o valor de K para 1. Isso pode provocar um furo na função quando esta for colocar um número par. Veja que o número 1 jamais será impresso em nenhum caso, pois já começamos a incrementar no início do laço. Mesmo assim, o número 1 poderá ser impresso. E o pior é que poderá ser impresso no EditPar.
Para contornar isso, poderemos testar o mutex logo no método clique de nosso botão. Ficará assim:
- Código:
procedure TForm1.Button1Click(Sender: TObject);
var
hThreadID : THandle;
ThreadID : DWord;
begin
WaitForSingleObject (Bandeira, INFINITE);
K := 1;
ReleaseMutex (Bandeira);
hThreadID := CreateThread (nil, 0, @TestaSecaoCritica, nil, 0, ThreadID);
Sleep (500);
hThreadID := CreateThread (nil, 0, @TestaSecaoCritica, nil, 0, ThreadID);
end;
Conclusões finais
* Aqui, vimos thread para uma única função. Você, em seus programas, poderá utilizar quantas funções achar necessárias para começar suas threads. Lembre-se que threads são funções. Funções podem chamar outras e até serem recursivas. Mas, somente quando a função primeira encerrar, sua thread terá encerrado.
* Você poderá utilizar mutex para qualquer recurso, inclusive em funções distintas. Veja que é uma segurança de que uma thread não irá bagunçar com outra thread.
* Quando usamos um mutex, todas as threads deverão utilizá-lo. Veja que é uma técnica de programação. Portanto, o programador tem que preocupar-se com seus recursos. Se instruímos nossas threads a verificar a bandeira antes de continuar, garantimos que nenhuma outra thread estará usando, e nem virá a usar, o recursos que desejamos enquanto nossa thread o utiliza.
* Não há vínculo algum entre o objeto mutex e o seu código. É só uma simples bandeira que você utiliza para controlar a seqüência e a utilização de recursos
Autor: Jossérgio