20
May 08

Delphi: CreateThread

:: articles :: by Gilberto Saraiva

Camaradas,

O CreateThread é uma função disponibilizada pela plataforma Windows ( API ) cedida pela Kernel32, ela é responsável pela criação de um objeto Thread no windows que efetua operações no processador sendo gerenciado pelo windows na parte da fila de processamento e outros eventos.

 Delphi |  copy code |? 
1
function CreateThread(lpThreadAttributes: Pointer;
2
  dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine;
3
  lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall;
4
{$EXTERNALSYM CreateThread}

  • O 1º parametro é responsável pela segurança e compartilhamento do Objeto Thread que vai ser criado.
  • O 2º parametro é responsável pela definição do tamanho do Stack, ou seja, você pode definir o tamanho que vai ser utilizado pela Thread nos operadores ASM, em todo caso, se lhe falta de entendimento específico sobre o assunto é bom deixar em 0 que ele assume o tamanho padrão 1MB.
  • O 3º parametro é responsável pela definição do endereço inicial da thread, pra entendimento rápido é o ponteiro de uma function do mesmo padrão abaixo:
     Delphi |  copy code |? 
    1
    type
    2
      TThreadStartProc = function (Parameter: Pointer): Integer; stdcall;
  • O 4º parametro é responsável pela definição da “chave mestra” no processo de criação de uma thread, o ponteiro do parametro, podendo esse parametro ser qualquer coisa desde que persista enquanto a thread existir ou até o inicio da mesma.
  • O 5º parametro é responsável pelo modo em que a thread vai ser criada, Suspensa (CREATE_SUSPENDED) ou Não suspensa (0), a API poderá receber outro parametro, o STACK_SIZE_PARAM_IS_A_RESERVATION, mas esse não é muito utilizado pois não tem funcionamento na plataforma Windows 2000.
  • O 6º parametro é responsável pela recepção do ID da Thread na lista de Objetos Thead do Windows, esse valor serve para processos de identificação de origem do processamento através da API GetCurrentThreadId.
  • O retorno da API CreateThread é o Handle da Thread, como sabemos Handles são números que identificam objetos Windows e através de APIs podemos controla-los com muita especialidade.

    Agora, se você chegou até aqui, é porque realmente está interessado em aprender a utilizar as tão famosas thread, e a primeira coisa que lhe digo:

  • É simples, não tem mito nem urucubaca sobre a criação de thread para processamentos específicos.
  • Lembre-se Processamentos Específicos, se você quiser uma interação com algum componente na interface gráfica você precisa de criar uma sincronia com a VCL.

    Então agora sem o susto, podemos iniciar a utilização:

    Primeiramente baixe o código-fonte abaixo, abra o delphi e compile. Execute o aplicativo, clique em Run e veja que alguns processos são disparados, mais especificamente 100 processos paralelos.

    Nesse teste eu criei um pequeno controle de sincronia boolean feita através da variável SimpleLock, perceba que ao iniciar o processo de adicionar linha no TMemo mmoLog eu coloco o valor dela em True, indicando que estou processando algo, e logo no final ao terminar o processo eu coloco o SimpleLock como false, indicando que já terminei o processo. Assim antes de iniciar o processo eu verifico se o SimpleLock está em True, e se estiver eu espero até que ele esteja em False para iniciar um novo processamento.

    Agora partindo para o entendimento direto do código

    Escrevi um record, que servira de parametro para a minha função da Thread, como preciso de passar o parametro como ponteiro ( Pointer ) escrevi o tipo PMyParallelParams que nada mais é que uma indicação que o podemos guardar um ponteiro do objeto TMyParallelParams.

     Delphi |  copy code |? 
    01
    02
    type
    03
      PMyParallelParams = ^TMyParallelParams;
    04
      TMyParallelParams = record
    05
        A, B: integer;
    06
        ThreadName: string;
    07
        Handle: THandle;
    08
        Callback: procedure(AParam: PMyParallelParams) of object;
    09
      end;
    10

    A variável A é responsável por guardar o tamanho do passo ( Step Size ) que deve ser feito para se atingir o valor da variável B.
    ThreadName o próprio nome já diz tudo, é aonde guardamos o nome que idenfica a thread das demais.
    Handle é o Handle do Objeto Thread para manipulação da própria Thread durante o processamento.
    Callback é o procedimento que a Thread deve chamar no final do seu processamento.

    Perceba que todos esses valores foram criados por mim, e não fazem parte de um Thread nativa, ou seja, o código foi estruturado para funcionar com essa estrutura e nenhum processo é automático, todas as variáveis tem sua razão de existir, coisa que você podera escrever também para seus processamento próprios sendo específico para cada tipo de finalidade desejada.

    A declaração da SimpleLock para sincronia e do procedimento de Callback que informaremos para a nossa Thread.

     Delphi |  copy code |? 
    1
    2
        ...
    3
      private
    4
        { Private declarations }
    5
      public
    6
        SimpleLock: boolean;
    7
        procedure ReceiveThreadEnd(AParam: PMyParallelParams);
    8
      end;
    9

    Como podemos notar a criação de uma Thread para uma finalidade específica que não dependa de outros processos se torna quase completamente paralela, então no meu caso, coloquei um nome na função bem em conchavo com a especialidade da mesma.

     Delphi |  copy code |? 
    01
    function MyParallelProc(AParam: PMyParallelParams): Integer; stdcall;
    02
    var
    03
      i: integer;
    04
    begin
    05
      i := 0;
    06
      while i < AParam^.B do
    07
      begin
    08
        i := i + AParam^.A;
    09
        Sleep(100);
    10
      end;
    11
      AParam^.Callback(AParam);
    12
      Dispose(AParam);
    13
      EndThread(0);
    14
      CloseHandle(AParam^.Handle);
    15
      Result := 0;
    16
    end;

    O código é bem simples, algumas notações da parte final devem ser feitas:

  • AParam^.Callback(AParam); é a chamada que fazemos ao Callback.
  • Dispose(AParam); Como não precisamos mais do Parametro, liberamos a memória dele para não ocorrer os comuns vazamentos de memória ( memory leaks ).
  • EndThread(0); Finalizamos o processamento da Thread perante o Windows, indicando que o processamento não obteve erros, indicado pelo parametro 0. Só é possível utilizar a API EndThread quando o GetCurrentThread for igual ao Thread que queremos finalizar, e como estamos na própria Thread não há possibilidades de valor diferente.
  • CloseHandle(AParam^.Handle); Fechamos o Objeto Thread no windows, finalizamos todos os espaços gastos pelo Windows para sua criação e persistência.
  •  Delphi |  copy code |? 
    01
    var
    02
      i: integer;
    03
      dwNull: DWORD;
    04
      Params: PMyParallelParams;
    05
    begin
    06
      SimpleLock := false;
    07
      lblStillRunning.Caption := '100';
    08
      for i := 1 to 100 do
    09
      begin
    10
        New(Params);
    11
        Randomize;
    12
        Params^.A := Random(10) + 1;
    13
        Params^.B := Random(100);
    14
        Params^.ThreadName := 'Thread' + IntToStr(i);
    15
        Params^.Callback := ReceiveThreadEnd;
    16
        Params^.Handle := CreateThread(nil, 0, @MyParallelProc, Params,
    17
          0, dwNull);
    18
      end;

    Esse é o código de quando clicamos no TButton btnRun. Este código é o coração de todo nosso processo de entendimento de como iniciar um processo paralelo com o CreateThread, então atenção as explicações (Em tópicos para facilitar).

  • Primeiro passamos o SimpleLock para false, para indicar que nenhum processo está sendo efetuado.
  • Depois colocamos o caption no Label que informa quantas thread estão ainda rodando. No caso iniciamos em 100 pois criaremos 100 processos paralelos.
  • Ai vem o Loop que cria as Threads, de 1 até 100, contruindo 100 Thread ao todo.
  • New(Params);. New inicia um ponteiro tipificado, no nosso caso teremos espaço na memória para guardar os dados que precisamos em Params: PMyParallelParams. Esta parte é estruturada juntamente com o Dispose na function MyParallelProc, que efetua a liberação da memória utilizada pelo ponteiro.
  • Executamos o Randomize para aumentar a possibilidade de não obtenção de resultados semelhantes na utilização do Random.
  • Iniciamos a configuração do Params, que será passado para a Thread na function MyParallelProc. A variável A é configurada com um número randomico de 1 até 11.
  • A variável B é configurada com um número randomico de 0 até 100.
  • Colocamos o nome da Thread que iremos criar de acordo com o contador do loop.
  • Configuramos o Callback, sendo esse o procedimento ReceiveThreadEnd
  • Fazemos a chamada da API CreateThread com os parametros necessários.
    nil para definir que não teremos configuração de segurança nem tampouco de compartilhamento.

    0 para indicar que o StackSize vai ser o padrão, 1Mb.

    @MyParallelProc para indicar aonde está a function MyParallelProc que será o inicío da Thread.

    Params para indicar que o parametro a ser passado é o ponteiro do nosso record. Lembre-se que pra cada passo no Loop criamos um novo Params, no uso do New(), então até que nenhuma Thread chegue ao seu términio teremos 100 objetos TMyParallelParams na memória.

    0 para indicar que a Thread deve ser iniciada imediatamente após retornar o Handle da mesma.

    dwNull como não utilizaremos o ThreadID em nada, não precisamos guardar este valor.

  • A estrutura básica então é essa, não vou comentar muito sobre a parte da sincronia com a VCL pois isso será assunto para um outro artigo.

    Qualquer dúvida, sugestão, crítica ou agradecimentos, fique avontade.

    Lembrando mais uma vez, se você possuí material interessante sobre Delphi e outros,
    você pode postar o site na CommunityLinks

    Abraço a todos



    19
    May 08

    Delphi: CreateMutex

    :: articles :: by Gilberto Saraiva

    Camaradas,

    O CreateMutex é uma função disponibilizada pela plataforma Windows ( API ) que cria uma sinalização a nível de processo na Kernel32, ou seja, você poderá criar uma sinalização mutex que lhe servirá para várias finalidades e uma das principais é impedir que o mesmo aplicativo seja executado duas vezes.

     Delphi |  copy code |? 
    1
    function CreateMutex(lpMutexAttributes: PSecurityAttributes; bInitialOwner: BOOL; lpName: PChar): THandle; stdcall;
    2
    {$EXTERNALSYM CreateMutex}

    O exemplo abaixo impede a execução do mesmo aplicativo por mais de uma vez no mesmo computador:

     Delphi |  copy code |? 
    01
    program Project1;
    02
     
    03
    uses
    04
      Forms,
    05
      Windows,
    06
      Dialogs,
    07
      Unit1 in 'Unit1.pas' {Form1};
    08
     
    09
    {$R *.res}
    10
     
    11
    var
    12
      hMutex: THandle;
    13
    begin
    14
      hMutex := CreateMutex(nil, true, PChar('MeuNomeUnico'));
    15
      if (hMutex <> 0) and (GetLastError = 0) then
    16
      begin
    17
        Application.Initialize;
    18
        Application.CreateForm(TForm1, Form1);
    19
        Application.Run;
    20
      end else
    21
        ShowMessage('Aplicativo já está rodando.');
    22
    end.
    23

    Utilizando basicamente, o 3º parametro do CreateMutex é aonde informamos qual o nome único utilizaremos, para que na tentativa de criação da sinalização, a Kernel32 verifique se há alguma sinalização de nome igual ( A verificação é em case-sensitive ).

    Já que vimos a utilização básica de uma sinalização a nível de kernel podemos entender como o sistema funciona.

  • Ao utilizar o CreateMutex, criamos uma sinalização mutex controlada e gerenciada pela Kernel32, ou seja, seu aplicativo em nada muda ao chamar esse API.
  • Como o controle de todos os processos é feito pela Kernel32, em hipótese de travamento de seu programa não há possibilidade da sinalização Mutex ficar retida, pois ao detectar que o processo foi extinto a Kernel32 retira a sinalização automaticamente.
  • Após criada uma sinalização Mutex, enquanto ela não for fechado ( Utiliza-se CloseHandle para essa finalidade ), a sinalização ficará de posse da primeira execução do aplicativo. Então quando executado pela segunda vez, ao tentar criar uma sinalização com o mesmo nome, a Kernel32 retorna um erro, aonde se é interpretado que a sinalização já existe e assim indicando que o processo já está executado.
  • A sinalização mutex ( Mutex Object ) poderá ser utilizada de várias formas, compreende-se em sinalização de controle de acessos à algum ponto ou objeto comum a mais de um processo ( Threads ).
  • A matéria é muito extensa, engloba Threads, Processos, Sinalização e Semaforização de acessos além da vasta acessibilidade de Eventos Kernel32, área que já implica no entendimento de Tokens Privileges. Ou seja, assunto para mais de um artigo :D .

    Abraço a todos