|
Usuários |
|
68 Usuários Online
|
|
[Artigos]
[Intermediário] - Tornando recursos Thread-Safe no Delphi |
Publicado por rboaro : Terça, Agosto 13, 2013 - 07:49 GMT-3 (702 leituras)
3 Comentários Enviar para um amigo Versão para impressão
|
Já falamos aqui da necessidade de utilizar threads para criar processos paralelos, fazendo com que esses processos sejam mais ágeis e torne possível para o usuário continuar suas tarefas mesmo que, internamente o aplicativo esteja executando diversas tarefas pesadas. Porém, um problema do ambiente multi-thread é a concorrência. Imagine que duas ou mais threads façam uso do mesmo recurso, caso o acesso a esse recurso seja simultâneo e este não esteja preparado para esse cenário, é possível que sua aplicação entre em deadlock. Para evitar esses problemas existe a sessão crítica, disponível no Delphi através da classe TCriticalSection. A ideia é muito simples, um recurso compartilhado entre threads é um recurso crítico, sendo assim, antes de fazer uso desse recurso, é necessário indicar que o processamento estará entrando em uma sessão crítica e consecutivamente, indicar que o processamento saiu da sessão crítica. Por exemplo:
function TGerenciadorRecurso.AdicionarObjeto(AObjeto: TObject):integer;
begin
Self.FCritical.Enter();
try
Result := Self.FRecurso.Add(AObjeto);
finally
Self.FCritical.Release();
end;
end;
Com o TCriticalSection, somente uma thread por vez poderá entrar na sessão crítica, ou seja, quando uma thread acionar o comando:
Self.FCritical.Enter();
Qualquer outra thread que precisar entrar em uma sessão crítica do objeto FCritical (do tipo TCriticalSection) terá que esperar o comando:
Self.FCritical.Release();
Ou seja, com isso criamos um enfileiramento para consumo do recurso. Funciona como um semáforo, a partir do momento em que entramos em uma sessão crítica, o sinal está vermelho para qualquer outro processamento que tenha a necessidade de entrar nessa sessão. É importante salientar que o objeto de sessão crítica, deve estar no escopo do objeto concorrente, para que se faça sentindo o uso desse recurso, sendo assim, ao se projetar uma classe que contenha algum recurso crítico que deverá ser acessado por múltiplos processamentos paralelos, essa classe deverá ter um objeto de controle sessão crítica (TCriticalSection) para viabilizar de forma segura esse cenário.
type
TGerenciadorRecurso = class
private
FRecurso : TObjectList;
FCritical : TCriticalSection;
public
function AdicionarObjeto(AObjeto : TObject):integer;
constructor Create;
destructor Free;
end;
implementation
constructor TGerenciadorRecurso.Create;
begin
Self.FRecurso := TObjectList.Create;
Self.FRecurso.OwnsObjects := true;
Self.FCritical := TCriticalSection.Create;
end;
destructor TGerenciadorRecurso.Free;
begin
Self.FRecurso.Free;
Self.FCritical.Free;
end;
Isso porque o próprio objeto deve ter controle de seus recursos. Porém, existe um problema com o uso do TCriticalSection, esse recurso pode deixar o processo como um todo mais lento de forma desnecessária. Imagine que, seguindo nosso exemplo, eu vou escrever em um recurso crítico, mas também vou fazer leitura de seu conteúdo. O processo de leitura não necessariamente irá alterar o estado do recurso, diferente do processo de escrita. Sendo assim o processo de escrita é crítico e o de leitura não? Errado, imagine que enquanto você está fazendo a leitura do recurso, outro processo, realiza uma edição exatamente no ponto que o primeiro processo está realizando a leitura, o que poderia acontecer ao seus processos? Sendo assim, os dois processos são críticos, mas, chegamos a conclusão que podemos ter N leitoressimultâneos, porém somente um escritor, sendo que, se tiver pelo menos um leitor, não podemos realizar o processo de escrita e visse versa. Esse é o problema do TCriticalSection, com ele somente é possível um leitor por vez. Para resolver esse problema, existe a classeTMultiReadExclusiveWriteSynchronizer.
O nome já é bem claro, Multi Read Exclusive Write (tradução livre: múltipla leitura, escrita exclusiva). Então, adaptando nosso exemplo para esse cenário e incluindo um método para leitura do recurso crítico, ficamos com o seguinte código de interface:
interface
uses
System.Contnrs,
System.Classes,
System.SysUtils;
type
TGerenciadorRecurso = class
private
FRecurso : TObjectList;
FMultiReadExclusiveWrite : TMultiReadExclusiveWriteSynchronizer;
public
function AdicionarObjeto(AObjeto : TObject):integer;
function PegarObjeto(const AIdx : Integer):TObject;
constructor Create;
destructor Free;
end;
E o seguinte código de implementação
implementation
function TGerenciadorRecurso.AdicionarObjeto(AObjeto: TObject):integer;
begin
Self.FMultiReadExclusiveWrite.BeginWrite();
try
Result := Self.FRecurso.Add(AObjeto);
finally
Self.FMultiReadExclusiveWrite.EndWrite();
end;
end;
constructor TGerenciadorRecurso.Create;
begin
Self.FRecurso := TObjectList.Create;
Self.FRecurso.OwnsObjects := true;
Self.FMultiReadExclusiveWrite := TMultiReadExclusiveWriteSynchronizer.Create;
end;
destructor TGerenciadorRecurso.Free;
begin
Self.FRecurso.Free;
Self.FMultiReadExclusiveWrite.Free;
end;
function TGerenciadorRecurso.PegarObjeto(const AIdx: Integer): TObject;
begin
Self.FMultiReadExclusiveWrite.BeginRead();
try
Result := Self.FRecurso.Items[AIdx];
finally
Self.FMultiReadExclusiveWrite.EndRead();
end;
end;
Note que o método AdicionarObjeto inicia e finaliza uma sessão crítica de escrita, enquanto que o método PegarObjeto inicia e finaliza uma sessão crítica de leitura. Sendo assim, N threads poderão executar o método PegarObjeto de forma simultânea, porém, somente uma poderá executar o método AdicionarObjeto por vez. Inclusive, se algum processo estiver no bloco de leitura do método PegarObjeto, nenhuma thread poderá entrar na sessão de escrita do método AdicionarObjeto. Controlar sessões críticas é fundamental no desenvolvimento de aplicações multi-thread. Caso não sejam implementados controles para os recursos concorrentes, são grandes as chances de sua aplicação entrar em deadlock e você perder muitos dias para descobrir a causa dessa falha.
Diego Garcia
http://drgarcia1986.wordpress.com
http://twitter.com/drgarcia1986
|
|
Comentários | |
| | Comentários pertencem aos seus respectivos autores. Não somos responsáveis pelo seus conteúdos. |
por: jffonseca (joao@treinacon.com.br)
: Ago 15, 2013 - 10:55 (Informações sobre o membro | Enviar uma mensagem)
http://http://
|
Cara, sensacional!!!
Agora, como posso implementar isso em um ambiente multicamadas com DATASNAP, para threads acessando o banco de dados?
Estou tendo problemas utilizando uma única conexão TSQLConnection na aplicação Servidora, que é acessada para consultas normais dos formulários no lado Cliente e que quando uma thread de consulta de agenda é disparada, ocorre um erro: Error reading data from connection. O erro já é até conhecido e relatado pelo Eric Sasse, que segundo ele ocorre por ter um único TSQLConnection.
Gostaria de uma ajuda para solucionar o problema.
Um grande abraço.
|
|
|
Edição 112 |
|
|
50 Programas Fontes |
|
|
Produtos |
|
|