Threads em Sistemas Operacionais

Durante nossa jornada pela Engenharia de Software, passamos rapidamente da escrita de scripts sequenciais para a necessidade de sistemas que realizam mĂșltiplas tarefas simultaneamente. Para entender como um navegador web pode baixar uma imagem, responder aos cliques dos usuĂĄrios e renderizar um vĂ­deo ao mesmo tempo, precisamos falar sobre Threads.

O que Ă© um(a) thread?

Em termos acadĂȘmicos, uma thread (ou linha de execução) Ă© a menor unidade de processamento que pode ser agendada por um sistema operacional. Ela reside dentro do contexto de um processo.

De acordo com Tanenbaum (2016, p. 53), enquanto um processo Ă© uma maneira de agrupar recursos relacionados (como espaço de endereçamento, arquivos abertos e sinais), a thread Ă© a entidade executada na CPU. Podemos imaginar que se o processo Ă© o “contĂȘiner” de recursos, a thread Ă© o “fluxo” de execução de instruçÔes.

Processos vs. Threads

Diferentemente dos processos, que são isolados uns dos outros por questÔes de segurança e integridade, as threads de um mesmo processo compartilham o mesmo espaço de memória (heap). No entanto, cada thread possui seu próprio:

  • Contador de Programa (PC): para saber qual instrução executar a seguir.
  • Registradores: para armazenar variĂĄveis de trabalho atuais.
  • Pilha (stack): para gerenciar chamadas de funçÔes e variĂĄveis locais.

Na Figura 1 podemos ver um desenho esquemĂĄtico que representa o conceito de ambiente monothread, ou seja, cada processo (cĂ­rculo) contĂ©m apenas um fluxo – thread – para operação (retĂąngulo dentro do cĂ­rculo).

Figura 1 – Ambiente monothread.
Fonte: Machado e Maia (2014).

Em contrapartida, na Figura 2 encontramos o desenho esquemĂĄtico do que seria um ambiente multithread. Um processo qualquer (cĂ­rculo) contĂ©m trĂȘs fluxosthreads – de operação (retĂąngulos) em seu ambiente, compartilhando alguns recursos com esses threads.

Figura 2 – Ambiente multithread.
Fonte: Machado e Maia (2014).

CaracterĂ­sticas fundamentais

As threads possuem propriedades que as tornam indispensĂĄveis para o desenvolvimento de software moderno:

  1. Compartilhamento de recursos: elas compartilham o cĂłdigo, os dados e os recursos do sistema operacional (como arquivos abertos) do processo pai.
  2. Economia (lightweight): criar uma thread Ă© muito mais “barato” em termos de processamento e memĂłria do que criar um processo novo, pois nĂŁo hĂĄ necessidade de replicar todo o espaço de endereçamento.
  3. ConcorrĂȘncia e paralelismo: em sistemas com mĂșltiplos nĂșcleos (multi-core), threads diferentes podem ser executadas em nĂșcleos diferentes simultaneamente (paralelismo real). Em nĂșcleos Ășnicos, o SO alterna entre elas tĂŁo rĂĄpido que cria a ilusĂŁo de simultaneidade (concorrĂȘncia).

A Figura 3 traz uma representação mais ampla da separação de fluxos de trabalho dentro de um pseudocódigo com vårios threads (Thread_1, Thread_2 e Thread_3) que realizam tarefas independentemente dentro do código geral.

Figura 3 – Desenho esquemĂĄtico ampliado para um processo com vĂĄrios threads.
Fonte: Machado e Maia (2014).

BenefĂ­cios e trade-offs

É importante sempre reforçamos que nĂŁo existe “bala de prata” na computação. Toda escolha envolve trocas. Neste caso, a introdução de threads traz uma complexidade exponencial ao projeto. Portanto, falemos sobre os prĂłs e os contras dessa tĂ©cnica.

BenefĂ­cios

  • Responsividade: em aplicaçÔes de interface grĂĄfica (GUI), uma thread pode processar a entrada do usuĂĄrio enquanto outra realiza um cĂĄlculo pesado em segundo plano, evitando que a tela “trave”.
  • Utilização de arquiteturas multi-core: permite que a aplicação utilize todo o poder de processamento do hardware disponĂ­vel.
  • Comunicação eficiente: como compartilham a mesma memĂłria, a comunicação entre threads (inter-thread communication) Ă© mais rĂĄpida que a comunicação entre processos.

Trade-offs

  1. CondiçÔes de corrida (Race conditions): quando duas threads tentam alterar o mesmo dado simultaneamente, o resultado final depende da ordem de execução, que é imprevisível. Isso exige o uso de mecanismos de sincronização como Mutexes e Semåforos.
  2. Deadlocks (Impasses): ocorre quando uma Thread A espera por um recurso reservado pela Thread B, enquanto a Thread B espera por um recurso reservado pela Thread A. Logo, ambas param para sempre.
  3. Dificuldade de debugging: erros em sistemas multithread sĂŁo frequentemente nĂŁo-determinĂ­sticos (difĂ­ceis de reproduzir), conhecidos como Heisenbugs.
  4. Overhead de troca de contexto: se houver threads demais para poucos nĂșcleos, o sistema gasta mais tempo alternando entre elas do que executando o cĂłdigo propriamente dito.

ConsideraçÔes finais

Dominar threads Ă© o que separa um programador que apenas “faz funcionar” de um Engenheiro de Software que projeta sistemas robustos. Como afirma Silberschatz, Galvin e Gagne (2015), o projeto de aplicaçÔes multithreaded tornou-se uma necessidade absoluta com a estagnação das velocidades de clock e o foco da indĂșstria em mĂșltiplos nĂșcleos de processamento.

Por isso, continue lendo os artigos deste site, alĂ©m de consultar as fontes de referĂȘncia utilizadas. Deixamos para os caros leitores os vĂ­deos abaixo onde explicamos um pouco mais sobre threads e fizemos atĂ© um exemplo de cĂłdigo em linguagem C criando um ambiente multithread. Obrigado pela leitura e bons estudos.

ReferĂȘncias

MACHADO, Francis Berenger; MAIA, Luiz Paulo. Arquitetura de sistemas operacionais. 5ÂȘ ed. Rio de Janeiro: LTC, 2014.

SILBERSCHATZ, Abraham; GALVIN, Peter B.; GAGNE, Greg. Sistemas Operacionais com Java. 9. ed. Rio de Janeiro: LTC, 2015.

TANENBAUM, Andrew S. Sistemas Operacionais Modernos. 4. ed. SĂŁo Paulo: Pearson Education do Brasil, 2016.