Classe de thread em Delphi
Raptor[Estúdio Mental]
http://mental.mentsu.com
Parte 2
O primeiro é o construtor:
construtor TThread.Create(CreateSuspended: Boolean);
começar
herdado Criar;
AdicionarThread;
FSuspended := CriarSuspended;
FCreateSuspended := CreateSuspended;
FHandle := BeginThread(nil, 0, @ThreadPRoc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
se FHandle = 0 então
raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
fim;
Embora este construtor não tenha muito código, ele pode ser considerado o membro mais importante porque o thread é criado aqui.
Após chamar TObject.Create através de Inherited, a primeira frase é chamar um processo: AddThread, e seu código fonte é o seguinte:
procedimento AddThread;
começar
InterlockedIncrement(ThreadCount);
fim;
Há também um RemoveThread correspondente:
procedimento RemoveThread;
começar
InterlockedDecrement(ThreadCount);
fim;
A função deles é muito simples, que é contar o número de threads do processo aumentando ou diminuindo uma variável global. Acontece que o processo Inc/Dec comumente usado não é usado para aumentar ou diminuir variáveis aqui, mas o par de processos InterlockedIncrement/InterlockedDecrement é usado. Eles implementam exatamente a mesma função, adicionando ou subtraindo uma à variável. Mas eles têm uma grande diferença, ou seja, InterlockedIncrement/InterlockedDecrement é thread-safe. Ou seja, eles podem garantir resultados de execução corretos em multithreading, mas Inc/Dec não. Ou, em termos da teoria do sistema operacional, trata-se de um par de operações "primitivas".
Tome mais um como exemplo para ilustrar a diferença nos detalhes de implementação entre os dois:
De modo geral, a operação de adição de um aos dados da memória possui três etapas após a decomposição:
1. Leia os dados da memória
2. Dados mais um
3. Armazene na memória
Agora suponha uma situação que pode ocorrer ao usar Inc para executar uma operação de incremento em um aplicativo de dois threads:
1. Thread A lê dados da memória (assumido como 3)
2. Thread B lê dados da memória (também 3)
3. Thread A adiciona um aos dados (agora é 4)
4. Thread B adiciona um aos dados (agora também é 4)
5. O thread A armazena os dados na memória (os dados na memória agora são 4)
6. O thread B também armazena os dados na memória (os dados na memória ainda são 4, mas ambos os threads adicionaram um a ele, que deveria ser 5, então há um resultado incorreto aqui)
Não existe esse problema com o processo InterlockIncrement, pois o chamado "primitivo" é uma operação ininterrupta, ou seja, o sistema operacional pode garantir que a troca de thread não ocorrerá antes que um "primitivo" seja executado. Portanto, no exemplo acima, somente após o thread A terminar de executar e armazenar os dados na memória, o thread B pode começar a buscar o número e adicionar um. Isso garante que mesmo em uma situação multithread, o resultado será o mesmo. correto.
O exemplo anterior também ilustra uma situação de "conflito de acesso de thread", e é por isso que os threads precisam ser "sincronizados" (Sincronizar). Isso será discutido em detalhes posteriormente, quando a sincronização for mencionada.
Falando em sincronização, há uma digressão: Li Ming, professor da Universidade de Waterloo, no Canadá, certa vez levantou objeções à tradução da palavra Sincronizar como "sincronização" em "sincronização de threads". razoável. "Sincronização" em chinês significa "acontecer ao mesmo tempo", e o objetivo da "sincronização de threads" é evitar que isso "aconteça ao mesmo tempo". Em inglês, Sincronizar tem dois significados: um é sincronização no sentido tradicional (Ocorrer ao mesmo tempo), e o outro é “Operar em uníssono” (Operar em uníssono). A palavra Sincronizar em "sincronização de threads" deve referir-se ao último significado, ou seja, "garantir que vários threads mantenham a coordenação e evitem erros ao acessar os mesmos dados". No entanto, ainda existem muitas palavras traduzidas incorretamente como esta na indústria de TI. Como se tornou uma convenção, este artigo continuará a ser usado aqui, porque o desenvolvimento de software é um trabalho meticuloso e o que deve ser esclarecido. nunca deve ser. Não pode ser vago.
Vamos voltar ao construtor do TThread. A coisa mais importante a seguir é esta frase:
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
A função Delphi RTL BeginThread mencionada anteriormente é usada aqui. Ela tem muitos parâmetros, os principais são o terceiro e o quarto parâmetros. O terceiro parâmetro é a função thread mencionada anteriormente, ou seja, a parte do código executada na thread. O quarto parâmetro é o parâmetro passado para a função thread, aqui é o objeto thread criado (ou seja, Self). Entre os outros parâmetros, o quinto é usado para definir o thread para suspender após a criação e não executar imediatamente (o trabalho de iniciar o thread é determinado de acordo com o sinalizador CreateSuspended em AfterConstruction), e o sexto é para retornar o ID do thread.
Agora vamos dar uma olhada no núcleo do TThread: a função de thread ThreadProc. O interessante é que o núcleo desta classe de thread não é um membro do thread, mas uma função global (porque a convenção de parâmetros do processo BeginThread só pode usar funções globais). Aqui está o seu código:
função ThreadProc(Thread: TThread): Inteiro;
var
FreeThread: Booleano;
começar
tentar
se não for Thread.Terminated então
tentar
Thread.Execute;
exceto
Thread.FFatalException := AcquireExceptionObject;
fim;
finalmente
FreeThread := Thread.FFreeOnTerminate;
Resultado: = Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished := Verdadeiro;
SignalSyncEvent;
se for FreeThread, então Thread.Free;
EndThread(Resultado);
fim;
fim;
Embora não haja muito código, é a parte mais importante de todo o TThread, pois esse código é o código que realmente é executado no thread. A seguir está uma descrição linha por linha do código:
Primeiro, determine o sinalizador Terminated da classe de thread. Se não estiver marcado como finalizado, chame o método Execute da classe de thread para executar o código do thread. executa o código Execute na classe derivada.
Portanto, Execute é a função de thread na classe de thread. Todo o código em Execute precisa ser considerado como código de thread, como prevenção de conflitos de acesso.
Se ocorrer uma exceção em Execute, o objeto de exceção será obtido por meio de AcquireExceptionObject e armazenado no membro FFatalException da classe de thread.
Finalmente, há alguns retoques finais antes do fio terminar. A variável local FreeThread registra a configuração do atributo FreeOnTerminated da classe de thread e, em seguida, define o valor de retorno do thread como o valor do atributo de valor de retorno da classe de thread. Em seguida, execute o método DoTerminate da classe thread.
O código para o método DoTerminate é o seguinte:
procedimento TThread.DoTerminate;
começar
se atribuído(FOnTerminate) então Synchronize(CallOnTerminate);
fim;
É muito simples, basta chamar o método CallOnTerminate através do Synchronize, e o código do método CallOnTerminate é o seguinte, que consiste em simplesmente chamar o evento OnTerminate:
procedimento TThread.CallOnTerminate;
começar
se Atribuído(FOnTerminate) então FOnTerminate(Self);
fim;
Como o evento OnTerminate é executado em Synchronize, ele não é essencialmente um código de thread, mas um código de thread principal (veja a análise de Synchronize posteriormente para obter detalhes).
Após executar OnTerminate, defina o sinalizador FFinished da classe de thread como True.
A seguir, o processo SignalSyncEvent é executado e seu código é o seguinte:
procedimento SignalSyncEvent;
começar
SetEvent(SincronizarEvent);
fim;
Também é muito simples, basta definir um Evento global: SyncEvent Este artigo descreverá o uso do Event em detalhes posteriormente, e o propósito do SyncEvent será explicado no processo WaitFor.
Em seguida, é decidido se a classe de thread será liberada com base na configuração FreeOnTerminate salva em FreeThread. Quando a classe de thread for liberada, consulte a implementação do destruidor a seguir para obter detalhes.
Finalmente, EndThread é chamado para encerrar o thread e o valor de retorno do thread é retornado.
Neste ponto, o tópico acabou completamente.
(continua)