• Nenhum resultado encontrado

Pools de Threads

No documento Fundamentos de Sistemas Operacionais (páginas 166-168)

Na Seção 4.1, descrevemos um servidor web com múltiplos threads. Naquele caso, sempre que o servidor recebe uma solicitação, ele cria um thread separado para atendê­la. Embora a criação de um thread separado seja, sem dúvida, melhor do que a criação de um processo separado, um servidor multithreaded apresenta, contudo, problemas potenciais. O primeiro problema está relacionado com o tempo necessário à criação do thread,  além  do  fato  de  que  esse  thread  será  descartado,  assim  que  concluir  seu  trabalho.  O  segundo problema é mais complicado. Se permitirmos que todas as solicitações concorrentes sejam atendidas em um novo  thread,  não  teremos  um  limite  para  o  número  de  threads  ativos  e  em  concorrência  no  sistema.  Um número ilimitado de threads pode exaurir os recursos do sistema, como o tempo de CPU ou a memória. Uma solução para esse problema é usar um pool de threads. A ideia geral por trás do pool de threads é a criação de múltiplos threads na inicialização do processo, bem como a inserção dos threads em um pool, onde eles ficarão esperando para entrar em ação. Quando um servidor recebe uma solicitação, ele desperta um thread desse pool — se houver um disponível — e passa para ele a solicitação de serviço. Uma vez que o thread conclua seu serviço, ele retorna ao pool e espera por mais trabalho. Se o pool não tem um thread disponível, o servidor espera até que um seja liberado. Os pools de threads oferecem esses benefícios: O atendimento de uma solicitação com um thread existente é mais rápido do que esperar a criação de um thread.

Um  pool  de  threads  limita  o  número  de  threads  existentes  em  determinado  momento.  Isso  é particularmente  importante  em  sistemas  que  não  podem  dar  suporte  a  um  grande  número  de  threads concorrentes.

3.

4.5.2

A  separação  da  tarefa  a  ser  executada  da  mecânica  de  criação  da  tarefa  permite­nos  usar  diferentes estratégias  para  execução  da  tarefa.  Por  exemplo,  ela  poderia  ser  organizada  no  schedule  tanto  para execução após um tempo de espera como para execução periódica.

O número de threads no pool pode ser estabelecido heuristicamente com base em fatores, como o número de  CPUs  no  sistema,  o  montante  de  memória  física  e  o  número  esperado  de  solicitações  de  clientes concorrentes. Arquiteturas de pool de threads mais sofisticadas podem ajustar dinamicamente o número de threads  no  pool  de  acordo  com  padrões  de  uso.  Essas  arquiteturas  oferecem  o  benefício  adicional  de possuírem um pool menor — consumindo menos memória — quando a carga no sistema é baixa. Discutimos uma arquitetura desse tipo, a Grand Central Dispatch da Apple, posteriormente, nesta seção.

A API Windows fornece várias funções relacionadas com os pools de threads. O uso da API de pools de threads é semelhante à criação de um thread com a função Thread_Create ( ), como descrito na Seção 4.4.2.  Aqui,  é  definida  uma  função  que  deve  ser  executada  como  um  thread  separado.  Essa  função  pode aparecer na forma a seguir:

DWORD WINAPI PoolFunction (AVOID Param) {    /*

   * essa função é executada como um thread separado.    */

}

Um ponteiro para PoolFunction ( ) é passado para uma das funções na API de pool de threads, e um thread do pool executa a função. Um membro desse tipo, pertencente à API de pool de threads, é a função QueueUserWorkItem ( ), que recebe três parâmetros:

LPTHREAD_START_ROUTINE Function —  um  ponteiro  para  a  função  que  deve  ser  executada como um thread separado PVOID Param — o parâmetro passado para Function ULONG Flags — flags indicando como o pool de threads deve criar e gerenciar a execução do thread Um exemplo de invocação de uma função seria o seguinte: QueueUserWorkItem(&PoolFunction, NULL, 0); Tal comando causa a invocação, por um thread do pool de threads, de PoolFuncion ( ) em nome do programador. Nesse caso, não são passados parâmetros para PoolFunction ( ). Já que especificamos 0 como flag, não fornecemos instruções especiais ao pool de threads para a criação de threads.

Outros  membros  da  API  Windows  de  pool  de  threads  incluem  utilitários  que  invocam  funções  em intervalos  periódicos  ou  quando  uma  solicitação  de  I/O  assíncrona  é  concluída.  O  pacote java.util.concurrent da API Java também fornece um utilitário de pool de threads.

OpenMP

O OpenMP é um conjunto de diretivas de compilador assim como uma API para programas escritos em C, C++  ou  FORTRAN  que  dá  suporte  à  programação  paralela  em  ambientes  de  memória  compartilhada. OpenMP  identifica regiões  paralelas  como  blocos  de  código  que  podem  ser  executados  em  paralelo. Desenvolvedores de aplicações inserem diretivas de compilador em seu código, em regiões paralelas, e essas diretivas  instruem  a  biblioteca  de  tempo  de  execução  do  OpenMP  a  executar  a  região  em  paralelo.  O programa em C, a seguir, ilustra uma diretiva de compilador acima da região paralela que contém o comando printf ( ):

#include <omp.h> #include <stdio.h>

int main(int argc, char *argv[]) {

4.5.3

   /* código sequencial */    #pragma omp parallel    {

      printf(“I am a parallel region.”);    }

   /* código sequencial */    return 0;

}

Quando OpenMP encontra a diretiva #pragma omp parallel

ele cria um número de threads equivalente ao número de núcleos de processamento do sistema. Assim, para um sistema dual­core, dois threads são criados; para um sistema quad­core, quatro são criados; e assim por diante. Todos os threads, então, executam simultaneamente a região paralela. À medida que cada thread sai da região paralela, ele é encerrado.

O  OpenMP  fornece  várias  diretivas  adicionais  para  a  execução  de  regiões  de  código  em  paralelo, incluindo a paralelização de loops.  Por  exemplo,  suponha  que  tenhamos  dois  arrays a e b de  tamanho  N. Queremos  somar  seu  conteúdo  e  colocar  o  resultado  no  array c.  Podemos  ter  essa  tarefa  executada  em paralelo usando o segmento de código a seguir, que contém a diretiva de compilador para a paralelização de loops for:

#pragma omp parallel for for (i = 0; i < N; i++) {    c[i] = a[i] + b[i]; }

O OpenMP divide o trabalho contido no loop for entre os threads que ele criou, em resposta à diretiva #pragma omp parallel for

Além  de  fornecer  diretivas  de  paralelização,  o  OpenMP  permite  que  os  desenvolvedores  escolham  entre vários níveis de paralelismo. Por exemplo, eles podem definir o número de threads manualmente. Também permite  que  os  desenvolvedores  identifiquem  se  os  dados  são  compartilhados  entre  threads  ou  se  são privados  de  um  thread.  O  OpenMP  está  disponível  em  vários  compiladores  comerciais  e  de  código­fonte aberto  para  os  sistemas  Linux,  Windows  e  MacOS  X.  Recomendamos  que  os  leitores  interessados  em aprender mais sobre o OpenMP consultem a Bibliografia no fim do capítulo.

No documento Fundamentos de Sistemas Operacionais (páginas 166-168)