Processos e Threads

Dalton Serey

Antes de entrarmos de vez no tema central do curso e de mergulharmos em aspectos técnicos mais específicos de desenvolvimento de sistemas web, precisamos ver (ou rever) alguns conceitos fundamentais sobre o funcionamento de computadores e sobre como programas são executados neles.

Cabe aqui um breve disclaimer. Os conceitos de processo e thread são vistos em detalhes na disciplina de Sistemas Operacionais. Nosso objetivo aqui é apenas permitir um primeiro contato com os conceitos, dada a natureza multi-processos de sistemas web.

Programa vs Programa

Primeiro, é importante que você saiba diferenciar entre programas e processos.

Um programa é meramente o texto, o código com as instruções necessárias para fazer uma máquina prover um serviço. Um programa é tipicamente armazenado como um arquivo em mídias digitais (disco, fita, etc). Tipicamente, existe no disco e nele se mantém sem qualquer mudança, se não for deliberadamente apagado ou alterado por agentes externos (pelo usuário, pelo sistema operacional, etc).

Um processo é uma instância de um programa em execução. Consiste no conjunto de recursos e dados que o sistema operacional e a máquina provêem como abstração para a execução de um programa. Um processo inclui a reserva de espaço de memória para uso exclusivo – o chamado espaço de endereçamento virtual (ou EEV), os dados sobre o estado de execução do programa, tais como o exato ponto do programa que está sendo executado (ou instruction pointer), valores dos registradores, e outros dados como referências aos arquivos e dispositivos em uso. Isso evidencia a natureza dinâmica de um processo em comparação a um programa. O processo existe e muda continuamente durante todo seu ciclo de vida e é destruído quando a execução do é concluída.

Curiosidade. Nos atuais sistemas 32 bits, o EEV permite, tipicamente, endereçar 4G endereços (como cada byte recebe um endereço, o EEV fica limitado a 4GB de memória). Nas máquinas de 64 bits, contudo, a situação muda significativamente. Em princípio, cada endereço poderia ter 64 bits. Os fabricantes atuais, contudo, limitam os endereços a 48 bits. Ainda assim, isso aumenta significativamente o tamanho do EEV. Cada processo, em arquiteturas de 64 bits, fica “limitado” a 256TB. Na prática, isso é muito mais do que qualquer processo no presente precisa.

Processo: instância de um programa em execução

Vale a penar reforçar uma palavra da definição: um processo é uma instância de um programa em execução. Ou seja, nada impede que um mesmo programa (código fonte) seja usado para criar simultaneamente diversos processos. E, de fato, isso é frequentemente feito. Pense em qualquer sistema operacional moderno. Não é raro que o usuário execute simultaneamente múltiplas instâncias de um mesmo programa (a título de informação, cada aba de um browser é um processo separado, em vários browsers modernos).

O conceito de processo permite que mesmo computadores que tenham um único processador executem múltiplos processos. Isso é conseguido justamente porque os hardwares modernos colaboram com o sistema operacional para prover a ilusão de que os processos executam em paralelo. De fato, em cada instante de tempo um único processo está sendo executado. O sistema operacional e o hardware periodicamente interrompem o processo em execuçao e o substituem por um dos demais processos existentes. Como esse mecanismo é muito mais rápido do que a expectativa de resposta do usuário, cria a impressão de paralelismo (por não ser paralelismo real, costuma ser chamada da multiprogramação). Isso, observe, só é possível porque cada processo detém todos os dados referentes à execução do programa. Logo, quando ele voltar a ser executado, todos esses dados serão deixados no exato estado em que estavam no momento da interrupção.

Processos permitem que o programador escreva programas se apoiando na ilusão de que ao ser executado disporá da memória, da CPU e do computador de forma exclusiva. Em qualquer sistema operacional moderno, qualquer programa será executado compartilhando a CPU e o computador com outros programas. É o conceito de processo provido pelo SO e pelo hardware que nos permitem programar sem que precisemos nos preocupar com isso.

Brevíssima definição de thread

Tipicamente, um processo tem uma única thread. Isto é, cada processo executa um único ponto do programa a cada momento. Sistemas operacionais modernos, contudo, permitem que um único processo tenha mais de uma thread, criando mais uma ilusão: a de que um único mesmo processo opera, internamente, de forma paralela. Ou seja, é como se um único processo se comportasse como vários processos paralelos que compartilham um mesmo espaço de endereçamento virtual. Por isso, threads são também chamadas, em alguns livros texto, de processos leves (lightweight processes).

O conceito pode parecer estranho para você neste momento, mas trata-se de um mecanismo poderoso em diversas aplicações em que é necessário que várias coisas aconteçam ao mesmo tempo. Esse é o caso, por exemplo, de servidores web na internet (tópico importante de nosso curso).

Servidores web são processos que atendem a requisições chegadas da internet. É claro que um mesmo servidor deve ser capaz de atender a diversas requisições simultaneamente. Se o servidor, contudo, só puder ter uma única thread, uma única requisição pode estar sendo atendida em cada instante. Uma solução seria criar um novo processo para cada requisição recebida. Criar processos, contudo, tem um custo computacional alto (de memória e tempo de CPU). Além disso, é evidente que o conjunto de dados servidos por um servidor e a lógica do serviço são a mesma. Logo, o mesmo executável seria usada em todos os processos. Além disso, em diversas situações os vários processos podem compartilhar certos dados. É nesse cenário que threads são uma solução interessante. Elas permitem que um único processo seja usado para atender às diversas requisições simultâneas.

Tipicamente, um servidor terá uma thread principal responsável por atender chegadas de requisições. Seu papel é criar uma nova thread exclusivamente para atender à requisição e voltar a esperar por novas requisições, enquanto a thread auxiliar atende efetivamente à requisição, buscando os dados e enviando-os ao requisitante. Quando a thread auxiliar concluir seu trabalho, é terminada. Como o custo de criar threads é significativamente menor que o de criar processos, o ganho de desempenho é significativo. Além disso, permite coordenar as atividades das threads, compartilhando dados em comum. Por exemplo, permite facilmente contabilizar recursos e/ou requisições.

Caso você ainda queira fazer leituras complementares, sugeriria o livro Modern Operating Systems de Andrew Tanenbaum. Para os propósitos deste curso, contudo, repito: basta ler os trechos de definições das seções introdutórias de cada tópico. Você precisa entender os conceitos, não como são implementados ou suportados pela máquina e pelo sistema operacional. De qualquer forma, a seguir listo os tópicos e as seções de minha cópia (Fourth Edition):

Outras leituras: