1 8 – Threads 8.1 – Introdução Uma thread, também chamada de tarefa, pode ser definida como uma parte ou rotina de um processo em execução que compartilha o mesmo espaço de endereçamento, mas tem seu próprio contexto de hardware e de software. Um processo pode ter várias threads, podendo estas tarefas serem executadas em paralelo. Até o final da década de 1970, os sistemas operacionais suportavam apenas processos com um único thread (monothread), ou seja, um processo com um único programa fazendo parte do seu contexto. Em 1979, foi introduzido o conceito de processo lightweight (peso leve), onde o espaço de endereçamento de um processo é compartilhado por vários programas. A partir do conceito de múltiplos threads (multithread), um processo pode ter diferentes partes do seu código sendo executado em paralelo. Como os threads de um mesmo processo compartilham o mesmo espaço de endereçamento, a comunicação entre os threads é mais rápida, visto que não envolve os mecanismos de comunicação entre processos. Atualmente, o conceito de multithread pode ser encontrado em diversos sistemas operacionais, entre eles o Microsoft Windows, Linux, Mac OS e o Solaris, da Sun Microsystems. 8.2 – Ambiente Monothread Um ambiente monothread suporta apenas um programa no seu espaço de endereçamento. Neste tipo de ambiente, aplicações concorrentes são implementadas apenas com o uso de múltiplos processos independentes ou subprocessos. Com o uso de múltiplos processos, cada funcionalidade do software implicaria na criação de um novo processo para atender a essa funcionalidade. 2 Outro problema é quanto ao compartilhamento do espaço de endereçamento. Como cada processo possui o próprio espaço de endereçamento isto comprometeria o desempenho da aplicação. 8.3 – Ambiente Multithread Em um ambiente multithread, não existe a ideia de programas associados a processos e sim a threads. O processo neste tipo de ambiente tem pelo menos uma thread em execução, mas pode compartilhar o espaço de endereçamento com outros inúmeros threads. Uma thread pode ser definida como uma sub-rotina de um programa que pode ser executada de forma assíncrona, ou seja, executada paralelamente ao programa. Um ambiente multithread possibilita a execução concorrente de subrotinas dentro de um mesmo processo. A grande vantagem do uso de threads é a possibilidade de minimizar a alocação de recursos do sistema, além de diminuir o overhead na criação, troca e eliminação de processos. Threads compartilham o processador da mesma maneira que os processos e passam pelas mesmas mudanças de estado. Dentro de um mesmo processo, threads compartilham o mesmo espaço de endereçamento, mas cada thread possui seu próprio contexto de hardware e contexto de software. Threads são implementados por uma estrutura de dados chamada Bloco de Controle de Thread(Thread Control Block – TCB). Esta estrutura armazena o contexto de hardware do thread e mais algumas informações como prioridade e estado de execução. Em um ambiente multithread, a unidade de alocação de recursos é o processo, onde todos os seus threads compartilham o espaço de endereçamento. Por outro lado, cada thread representa uma unidade de escalonamento, sendo assim, o sistema não seleciona o processo para ser executado, mas sim uma de suas threads. A grande diferença de aplicações monothreads e multithreads está no uso do espaço de endereçamento. Como os threads de um mesmo processo compartilham o mesmo espaço de endereçamento, não existe qualquer 3 proteção no acesso à memória, permitindo que um thread possa alterar facilmente os dados de outros e vice-versa. Programa concorrentes com múltiplos threads são mais rápidos que programas concorrentes implementados com múltiplos processos. Como os threads de um mesmo processo dividem o mesmo espaço de endereçamento, a comunicação entre eles é realizada de forma mais rápida e eficiente. Tabela 8.1 - Comparação entre Processo e Threads A utilização dos recursos do sistema computacional pode ser feita de forma concorrente pelos diversos threads, por exemplo, enquanto algumas aplicações realizam algumas tarefas em background, outras threads vão realizando operações de Entrada/Saída. 8.4 – Arquitetura e Implementação As threads podem ser oferecidas por meio de uma biblioteca de rotinas fora do kernel do sistema operacional (modo usuário), pelo próprio kernel (modo kernel), pela combinação dos dois modos (modo híbrido) ou por um modelo conhecido como scheduler activations ou ativações do escalonador. 8.4.1 – Threads em Modo Usuário Threads em modo usuário são implementadas pela própria aplicação e não pelo sistema operacional. Para isso, deve existir uma biblioteca que permita à aplicação realizar tarefas como a criação/eliminação de threads, troca de mensagens, escalonamento, etc. Neste modo o sistema operacional não sabe da existência dos threads, sendo a aplicação a única responsável por gerenciar essas threads. 4 Figura 8.1 – Threads em modo usuário Uma vantagem deste modo é a rapidez e eficiência, pois o sistema operacional não sabe da existência das threads, isso evita a mudança do modo de acesso. Uma desvantagem é que o sistema operacional gerencia cada processo como se o mesmo tivesse apenas uma única thread. Quando o processo em estado de execução vai para o estado de espera, todas as suas threads também são colocadas em estado de espera, mesmo estando no estado de pronto. Neste caso, é preciso criar rotinas que permitam a essas threads serem executadas. Esse controle é transparente para o usuário e para o sistema operacional. Um dos maiores problemas na implementação de threads em modo usuário é o tratamento individual de sinais. Como o sistema operacional reconhece apenas os processos e não threads, os sinais são enviados para o processo, que devem ser reconhecidos e encaminhados a thread responsável para o devido tratamento. 8.4.2 – Threads em Modo Kernel Threads em modo kernel são implementados diretamente pelo núcleo do sistema operacional, através de chamadas a rotinas do sistema que oferecem todas as funções de gerenciamento. Neste modo, o sistema operacional sabe da existência de cada thread e pode escaloná-los individualmente. 5 Figura 8.2 – Threads em modo kernel O grande problema deste modo é que threads em modo kernel utilizam system calls e, consequentemente, há várias mudanças de modo de acesso, enquanto que as threads em modo usuário fazem todo o tratamento sem a intervenção do sistema operacional. 8.4.3 – Threads em Modo Híbrido A arquitetura de threads em modo híbrido combina as vantagens de threads implementados em modo usuário com as dos threads em modo kernel. Um processo pode ter vários Threads em Modo Kernel e, este, por sua vez, ter vários threads em modo usuário. O núcleo do sistema operacional reconhece os threads em modo kernel e pode escaloná-los individualmente. Um thread em modo usuário pode ser executado por em thread em modo kernel em um determinado momento, e no instante seguinte, ser executado em outro. 6 Figura 8.3 – Threads em modo híbdrido O modo híbrido é mais flexível, pois implementa os dois modos em conjunto, mas também herda os problemas provenientes de ambos. Quando um thread em modo kernel realiza uma chamada bloqueante, todos os threads em modo usuário são colocados no estado de espera. Threads que utilizam vários processadores devem utilizar diferentes threads em modo kernel, uma para cada processador, o que influi no desempenho. 8.4.4 – Scheduler Activations (Ativações do Escalonador) O modelo ideal de implementação de threads deveria unir e utilizar as facilidades das threads em modo kernel com o desempenho e flexibilidade do modo usuário. Nesta arquitetura, ao invés de dividir as threads em modo usuário e modo kernel, como é feito no modelo híbrido, o kernel do sistema operacional troca informações com a biblioteca de threads utilizando uma estrutura de dados chamada scheduler activation. 7 Figura 8.4 – Scheduler Activations A maneira de alcançar um melhor desempenho é evitar as mudanças de modos de acesso. Caso uma thread utilize uma chamada ao sistema que a coloque em estado de espera, a própria biblioteca em modo usuário escalona outra thread. 8.5 – Modelos de Programação Um fator importante é a forma como as threads são criadas e eliminadas. Se uma aplicação cria um número excessivo de threads, poderá ocorrer uma sobrecarga no sistema, ocasionando uma queda de desempenho. Dependendo da aplicação, a definição do número de threads pode ser dinâmica ou estática. Quando a criação/eliminação é dinâmica, os threads são criados/eliminados de acordo com a demanda da aplicação, permitindo assim uma maior flexibilidade. Já em ambientes estáticos, o número de threads é definido na criação do processo onde a aplicação será executada. Para obter os benefícios do uso de threads, uma aplicação deve permitir que partes diferentes de seu código sejam executados em paralelo, de forma independente. Sistemas gerenciadores de banco de dados (SGBD), servidores de aplicações, servidores de arquivos ou impressão são exemplos onde o uso de múltiplos threads proporciona vantagens e benefícios.