logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog

follow us in feedly

Win32: threads

Uma thread é basicamente uma linha de execução independente, contida dentro de um processo. Ou seja, uma thread permite que um processo "faça varias coisas de forma simultânea", já que um processo pode conter várias threads. Todas as threads que fazem parte de um mesmo processo compartilham vários recursos, como o espaçamento de memória e os handles. Ou seja, elas acessam as mesmas variávies e podem usar os mesmos handles ao mesmo tempo.

Quando um processo é criado no Windows, é criado junto com ele a thread principal, que é thread que roda a função main ou WinMain. A partir daí é possível criar novas threads, como no exemplo abaixo:

#include "stdafx.h"
#include <windows.h>
#include <iostream>
 
 
using namespace std;

 
//
// WINAPI é um #define para __stdcall
//
DWORD WINAPI ThreadProc(void* lpv)
{
  //
  // vamos esperar até 500 ms com o nosso random() de pobre
  //
  Sleep(GetTickCount() % 500);
  return 0;
}
 
int main()
{
  DWORD dwThreadID;
  static const DWORD THREAD_COUNT = 20;
  HANDLE hThreads[THREAD_COUNT];
 
  //
  // vamos criar nossas threads
  //
  for(DWORD a = 0 ; a < THREAD_COUNT ; a++)
  {
    hThreads[a] = 
      CreateThread(NULL, // segurança, vamos deixar o default
      NULL,        // tamanho da pilha. O default é 1MB
      &ThreadProc,     // função da thread
      NULL,        // parâmetro da thread
      NULL,        // flag de criação. Podemos usá-lo para criar um thread suspensa
      &dwThreadID);
 
    //
    // O Sleep(0) diz ao scheduler do Windows que ele
    // pode agendar a próxima thread em espera, e nós
    // vamos para o fim da fila. 
    //
    Sleep(0);
  }
 
  //
  // coloque um breakpoint aqui, vá no menu Debug >> Windows >> Threads
  // e veja as threads que criamos na lista
  //
  __asm nop; // instrução x86 que não faz absolutamente nada
 
  //
  // Para esperar uma thread parar (o que os linux boys conhecem como 'join')
  // usamos WaitForSingleObject ou WaitForMultipleObjects. Essas funções
  // funcionam com handles de processos, threads, mutexes e outras coisas mais
  // que veremos depois
  //
  DWORD dw = WaitForSingleObject(
    hThreads[0], // handle
    100); // vamos esperar 100 ms
 
  if(dw == WAIT_TIMEOUT)
  {
    //
    // não retornou em 100ms... Vamos esperar para sempre
    //
    dw = WaitForSingleObject(hThreads[0], INFINITE);
  }
  else if(dw == WAIT_OBJECT_0)
  {
    //
    // esperamos menos de 100 ms
    //
  }
 
  //
  // agora vamos ver qual thread para primeiro
  //
 
  dw = WaitForMultipleObjects(
    THREAD_COUNT - 1, // quantos handles na array
    hThreads + 1,     // array de threads + 1, porque a thread 0 já acabou
    FALSE,            // não quero esperar por todas, se algum thread acabar está bom
    INFINITE);        // senta e espera
 
  DWORD qualThread = dw - WAIT_OBJECT_0;
 
  cout << "a thread mais rápida foi a " << qualThread << endl;
 
  //
  // agora vamos esperar todas de uma vez --------------|
  //                                                    v
  dw = WaitForMultipleObjects(THREAD_COUNT, hThreads, TRUE, INFINITE);
 
  //
  // Como a API é C, vamos fechar os handles. Lembrando que
  // o Windows faz isso automaticamente quando o processo acaba
  //
  for(DWORD a = 0 ; a < THREAD_COUNT ; a++)
    CloseHandle(hThreads[a]);
 
  return 0;
}

A única diferença entre as threads criadas posteriormente e a thread principal de um processo é que quando a thread principal acaba, todas as outras threads são forçosamente terminadas e o processo acaba - caso contrário uma thread esquecida poderia deixar o processo em um estado de limbo. Em um computador rodando Windows, em uma determinada hora, temos várias threads rodando, e a thread é única entidade que faz uso do processador. No Windows, um processo não "roda" nem é executado. O que "roda" é uma thread (que por sua vez pertence à um processo). Dessa forma, o scheduler (agendador) do Windows trabalha compartilhando e dividindo o uso do processador entre as threads do sistema, independente do processo que contém a thread.

Em uma de suas atuações, o scheduler do Windows (e de qualquer SO) funciona basicamente respondendo à uma interrupção de timer que acontece em intervalos de milissegundos. Quando essa interrupção ocorre, a thread atual é interrompida, e o scheduler toma seu lugar no processador para verificar se é necessário trocar de thread. Caso o quantum (tempo de cpu determinado para que a thread rode antes de outra thread assumir o processador) da thread atual tenha terminado, o scheduler salva os registradores que definem o estado atual da thread no thread context (veja a estrutura _CONTEXT no winnt.h) e carrega os registradores da próxima thread a ser executada. Com a nova thread pronta para utilizar o processador, o scheduler coloca a thread atual no fim da fila. Falando em código, o ponteiro da estrutura ETHREAD que representa a thread é colocada no fim de uma lista duplamente ligada que controla a fila de threads em estado ready.

Quando só existe um processador ou core no computador, o scheduler dá a impressão de que as threads estão sendo executadas simultaneamente, alternando entre elas dessa forma. Quando mais de um processador ou core estão disponíves, várias threads podem ser executadas simultaneamente, o que faz com que o scheduler precise distribuir as threads entre os N processadores disponíveis.

O tempo de interrupção citado é de 20 à 120ms, dependendo do processador e da versão do Windows. Nas versões Home, Professional e Business, os intervalos são de 20ms, para que o sistema seja mais responsivo ao usuário interativo. O intervalo de 120ms é usado nas versões Server, pois quanto maior o quantum, maior a probabilidade da thread conseguir atender a requisição em um quantum só, aumentando a vazão do servidor.

Outra situação onde o scheduler aparece é quando uma thread deve esperar uma operação de I/O. Nesse caso a thread perde o controle do processador antes que seu quantum acabe, e ela só será colocada novamente na lista de threads ready (prontas para serem executadas) quando essa requisição de I/O for respondida.

Como sempre, mais informações podem ser encontradas na MSDN (sobre as funções CreateThread e WaitForXXX), Wikipedia (sobre conceitos do sistemas operacionais, como quantum e thread) e no livro "Windows Internals" (sobre a implementação dos conceitos no kernel do Windows).


Em 15/04/2008 20:46, por Rodrigo Strauss


  
 
 
Comentários
LVR | em 17/04/2008 | #
Reclamei no outro post, sobre a questão Processo x Thread, pois sou um chato advindo dos Unices. Mas, gostei da explanação. O primeiro paragráfo é suficientemente esclarecedor, mesmo para quem nunca tenha ouvido nada respeito.

[]´s

---
LVR
MSP | em 19/08/2008 | #
É... concordo. O primeiro parágrafo esclarece bem o que é thread, principalmente se considerarmos as traduções brasileirindias dos livros importados.

Abraço

MSP
Negrão | em 19/05/2009 | #
Gostei da explicação, mas com certeza vc não tirou isso da sua cabeça. Gostaria de saber a fonte das informções.
Um abraço.
Rodrigo Strauss | website | em 20/05/2009 | #
http://msdn.microsoft.com/
Livros "Windows Internals", "Programming Windows 95" e "Advanced Windows".
Windows Platform SDK
Algo a dizer?
Nome:


Site:


E-mail:


Escreva o número vinte e seis:


 Não mostre meu e-mail no site, não serve pra nada mesmo...

Comentário





Os comentários devem ser sobre assuntos relativos ao post, eu provavelmente apagarei comentários totalmente offtopic. Se quiser me enviar uma mensagem, use o formulário de contato. E não esqueça: isso é um site pessoal e eu me reservo o direito de apagar qualquer comentário ofensivo ou inapropriado.
rebarba rebarba
  ::::