O Paralelismo no Python: Threads vs. Processos - Parte 1

O Paralelismo no Python: Threads vs. Processos - Parte 1

No desenvolvimento de software, o paralelismo desempenha um papel fundamental na melhoria do desempenho e eficiência do aplicativo. A capacidade de executar várias tarefas simultaneamente por meio do uso de threads (ou threads em espanhol) torna-se uma estratégia fundamental para reduzir os tempos de processamento e otimizar o funcionamento dos programas.

Outra ferramenta amplamente utilizada é a programação multiprocesso, que requer um tipo diferente de abordagem ao programar aplicativos.

Nesta primeira parte, vamos nos concentrar em explorar o uso de threads em Python, uma linguagem de programação amplamente utilizada atualmente.

Para entender melhor como os threads funcionam, forneceremos exemplos de código que ilustram sua implementação em situações práticas para ajudá-lo a se familiarizar com os conceitos e obter o máximo dessa poderosa ferramenta de programação paralela.


O que são os threads?


No contexto da programação, um thread refere-se a uma sequência de instruções que podem ser executadas independentemente dentro de um processo. Os threads permitem que as tarefas sejam executadas simultaneamente, o que significa que várias tarefas podem ser executadas simultaneamente, melhorando assim o desempenho dos aplicativos.

Em Python, os threads são implementados por meio do módulo de threading, que fornece uma interface para criar e controlar threads com facilidade.

Threads para tarefas de E/S


Os threads são especialmente úteis para lidar com operações de E/S. Alguns exemplos desse tipo de operação são:

  • Ler e gravar dados em um arquivo.
  • Imprima os resultados de um programa na tela.
  • Conecte-se a um banco de dados e troque informações.
  • Envie dados pela rede, por exemplo, usando protocolos como HTTP, TCP/IP, etc.
  • Ler e gravar dados de um dispositivo de armazenamento externo, como um USB.
  • Interaja com o sistema operacional para realizar operações.

Mais tarde, vamos expandir as razões pelas quais isso acontece. Primeiro vamos ver um exemplo.

Os scripts a seguir mostram duas implementações de um programa simples que executa tarefas de E/S. Nesse caso, a tarefa é fazer um request à página oficial do Python. O programa deve executar a tarefa 4 vezes.

Implementação sem utilizar os threads


Esta implementação não usa threads. Basta iterar 4 vezes e executar a função task_io. A função é responsável por fazer um request ao python.org e então imprimir o código de status no console. O tempo total para executar esse script é de 567 milissegundos.

💡
OBS: se você executar o script, obterá outro tempo total. Isso ocorre porque depende de várias variáveis, como a velocidade da Internet, o congestionamento da rede e o servidor que está sendo chamado.


Implementação com os threads


Dissemos que os threads são muito bons na execução de tarefas de E/S. Neste exemplo, estamos fazendo solicitações a um servidor externo, que é uma tarefa de E/S. Vamos reescrever o código acima, mas agora usando threads com a biblioteca oficial do Python.

Novamente, os tempos podem variar, mas, em média, esse script leva 195 milissegundos para ser executado. Esta é uma redução de tempo de execução de 65 %! O resultado é incrível, mas por quê? Nesta implementação, 4 threads são criadas para executar a tarefa em paralelo. Isso significa que ao invés de fazer uma requisição e esperar que ela termine antes de executar a próxima, cada thread se encarrega (simultaneamente) de chamar o servidor e não precisa esperar que as outras terminem.

Agora vamos ver como os threads podem ser usados ​​para outros tipos de tarefas.

Threads para tarefas intensivas da CPU


Tarefas intensivas em CPU são aquelas que exigem um alto nível de poder de processamento da Unidade Central de Processamento (CPU). Essas tarefas envolvem o uso extensivo do poder de computação da CPU para realizar operações complexas e exigentes de computação.

Para lidar com essas tarefas com eficiência, atenção especial deve ser dada à otimização e eficiência do código, pois elas exigem uma quantidade significativa de recursos da CPU e podem afetar diretamente o desempenho geral do sistema.

Alguns exemplos de tarefas intensivas da CPU são:

  • Compactação e descompactação de arquivos.
  • Processamento de imagem.
  • Criptografia.
  • Compilação do código-fonte.
  • Cálculos matemáticos complexos.
  • Algoritmos de Inteligência Artificial.

Os threads nos ajudaram a otimizar nossa tarefa de E/S. Por que não usá-los para tarefas intensivas de CPU? Vamos fazê-lo!

Implementação sem utilizar os threads


O programa a seguir pode parecer complexo, mas não é. A função Fibonacci é definida, parte de nossa tarefa intensiva de CPU, pois é um cálculo matemático que consome tempo de CPU.

A tarefa intensiva consiste em obter o número 5 de Fibonacci cerca de 100 mil vezes. Embora a tarefa não tenha um propósito real, ela é suficiente para simular um cálculo matemático muito complexo.

Como no exemplo anterior, a tarefa é executada 4 vezes em um loop. Terminada a primeira, executa-se a segunda e assim sucessivamente. O tempo total de execução do programa é de 688 milissegundos.

💡
OBS: novamente, o tempo de execução pode variar dependendo da velocidade do seu PC e da disponibilidade de recursos do sistema no momento em que o script é executado.


Implementação con uso de threads


Agora vamos ver o exemplo implementado com threads. Novamente, criamos 4 threads para executar a tarefa 4 vezes simultaneamente, 1 vez para cada thread. No exemplo acima obtivemos uma grande redução no tempo de processamento.

Esperamos um resultado semelhante, certo?


Executando este programa, obtemos um tempo total de processamento de 695 ms. Como isso é possível? Nada melhorou. Na verdade, era até um pouco pior do que a versão sem fio. Isso se deve a um mecanismo exclusivo do Python chamado GIL.

O que é o GIL no Python?


O GIL (Global Interpreter Lock) é um recurso do Python que garante a consistência dos dados internos do interpretador. Ele funciona como um mecanismo de bloqueio que permite que apenas um thread execute o código Python por vez, mesmo em servidores com vários núcleos de CPU. Isso limita o desempenho de aplicativos Python que tentam usar vários threads para executar tarefas intensivas de CPU em paralelo.

Ao executar tarefas intensivas de E/S, muito do tempo do programa é gasto aguardando respostas do sistema ou dispositivo que está sendo acessado. Em outras palavras, não é necessário muito poder de processamento da CPU para fazer uma solicitação a uma página da Web ou outro tipo de operação de E/S. Por causa disso, os encadeamentos funcionam de maneira ideal para esse tipo de tarefa. O GIL tem intervenção mínima e pode aproveitar ao máximo o paralelismo disponível.

Embora threads não sejam eficientes para programas que executam tarefas intensivas de CPU, existem outras ferramentas no campo da engenharia de software que podem ser úteis. Na próxima parte, exploramos uma delas: a programação multiprocesso, semelhante às threads na busca de otimizar um programa por meio do paralelismo. No entanto, sua implementação é diferente e possui limitações que os threads não possuem. Portanto, é essencial entender como ele funciona para aproveitá-lo adequadamente.

Conclusão


Threads em Python são uma ferramenta poderosa para obter simultaneidade e melhorar o desempenho do aplicativo, especialmente para operações intensivas de E/S. Eles permitem que várias tarefas sejam executadas simultaneamente e façam uso eficiente dos recursos disponíveis.

No entanto, é importante ter em mente as limitações do Python GIL e entender que threads não são a solução ideal para tarefas intensivas de CPU. Nesses casos, outras ferramentas como programação multithread podem ser exploradas, algo que veremos na segunda parte.

Ao dominar o uso de threads em Python e entender quando e como aplicá-los adequadamente, os desenvolvedores podem criar aplicativos mais eficientes e responsivos, melhorando assim a experiência do usuário e o desempenho geral do software.

Vejo você na próxima parcela! Saudações!

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.