Fundamentos de Programação Concorrente com Múltiplos Processos em Python

O que é um processo

Um procseso é uma instância em execução de um programa, representando uma tarefa ativa. A CPU é responsável por realizar essas tarefas. Em sistemas com núcleo único, a concorrência é simulada alternando rapidamente entre processos. Por exemplo, um desenvolvedor pode gerenciar múltiplas atividades, como escrever código, executar testes e revisar documentação, alternando entre elas para criar a ilusão de simultaneidade.

Diferença entre processo e programa

Um programa é um conjunto estático de instruções armazenado em disco, enquanto um processo é a execução dinâmica dessas instruções. Considere uma analogia culinária: uma lista de ingredientes e instruções para um prato é o programa, e o ato de preparar e cozinhar o prato é o processo. Cada execução de um mesmo programa gera um processo distinto, como abrir múltiplas instâncias de um editor de texto para documentos diferentes.

Concorrência versus paralelismo

Concorrência refere-se à capacidade de um sistema lidar com múltiplas tarefas, mesmo que em um único núcleo de CPU, através de alternância rápida. Paralelismo implica a execução simultânea real de tarefas em múltiplos núcleos. Em concorrência, um garçom em um restaurante pode atender várias mesas alternando atenção, dando a impressão de serviço simultâneo. Em paralelismo, vários garçons atendem mesas ao mesmo tempo.

Síncrono/assíncrono e bloqueante/não bloqueante

Síncrono: Uma chamada síncrona aguarda até que a tarefa seja concluída antes de retornar. Exemplos incluem multiprocessing.Pool.apply() ou concurrent.futures.ProcessPoolExecutor().submit(func).result(), onde o processo bloqueia até obter o resultado.

# Exemplo síncrono
from concurrent.futures import ProcessPoolExecutor

def tarefa_pesada(dado):
    # Simulação de processamento
    return dado * 2

executor = ProcessPoolExecutor(max_workers=2)
futuro = executor.submit(tarefa_pesada, 10)
resultado = futuro.result()  # Bloqueia até a conclusão
print(resultado)

Assíncrono: Uma chamada assíncrona retorna imediatamente, notificando o chamador após a conclusão. Por exemplo, multiprocessing.Pool().apply_async() ou submit() sem chamar result() de imediato, permitindo que o processo continue outras tarefas.

# Exemplo assíncrono
from concurrent.futures import ThreadPoolExecutor

def tarefa_leve(dado):
    return dado + 10

executor_assinc = ThreadPoolExecutor(max_workers=3)
futuro_assinc = executor_assinc.submit(tarefa_leve, 5)
# Não bloqueia aqui; pode-se verificar o estado depois
if futuro_assinc.done():
    print(futuro_assinc.result())

Bloqueante: Uma operação bloqueante suspende o processo até que uma condição seja satisfeita, como esperar por dados em um socket. Isso difere de síncrono, onde o processo pode estar ativo mas aguardando lógicamente.

Não bloqueante: Uma operação não bloqueante retorna imediatamente mesmo que o resultado não esteja disponível, evitando a suspensão do processo.

Resumo: Síncrono/assíncrono descreve a forma de chamada de funções, enquanto bloqueante/não bloqueante refere-se ao estado do processo durante a espera.

Criação de processos

Processos são criados de diversas formas em sistemas operacionais: durante a inicialização do sistema, por outros processos (como subprocessos em Python), por interações do usuário ou em tarefas em lote. Em sistemas UNIX, o sistema de chamada fork() é usado para criar uma cópia do processo pai, enquanto no Windows, createProcess() gerencia a criação e carregamento do programa. Após a criação, processos pai e filho possuem espaços de endereçamento isolados, garantindo independência de memória.

Término de processos

Um processo pode encerrar por motivos como saída normal (ex: usuário fecha uma janela), erros de execução (ex: arquivo não encontrado), exceções graves (ex: acesso inválido à memória) ou ser encerrado por outro processo (ex: comando de encerramento forçado). O sistema operacional gerencia essas transições para liberar recursos.

Hierarquia de processos

Em sistemas UNIX, os processos formam uma estrutura hierárquica em árvore, com o processo init como raiz. Processos pai e filho podem pertencer a grupos para sinalização. Em sistemas Windows, não há uma hierarquia estrita; os processos são tratados de forma independente, embora o processo pai possua um identificador (handle) para controle.

Estados de um processo

Os processos podem transitar entre estados: em execução (utilizando a CPU), bloqueado (aguardando eventos, como I/O) ou pronto (aguardando alocação de CPU). O sistema operacional gerencia essas transições para otimizar o uso de recursos. Por exemplo, um processo pode ser bloqueado ao ler de um disco, permitindo que outro processo utilize a CPU.

Implementação da concorrência

A concorrência é implementada através de interrupções de hardware e troca de contexto. O sistema operacional mantém uma tabela de processos com entradas que incluem o contador de programa, ponteiro de pilha e estado do processo. Quando um processo é interrompido, seu contexto é salvo para restauração posterior, garantindo continuidade eficiente.

Tags: Python Multiprocessing concurrent.futures Concorrência paralelismo

Publicado em 7-6 00:56