Paradigmas de Linguagens de Programação em Python: Python concorrente (Part. 11)
A programação concorrente é essencial para lidar com tarefas simultâneas e melhorar o desempenho de aplicações que precisam fazer mais de uma coisa ao mesmo tempo. Neste artigo, vamos explorar como Python trata a concorrência, os conceitos-chave envolvidos, e como aplicar isso de forma eficaz — sem cair em armadilhas comuns.
Para ilustrar esse processo, vamos imaginar que somos Nico Robin, a arqueóloga de One Piece, tentando decifrar os antigos Poneglyphs. Assim como um desenvolvedor precisa entender, executar e corrigir seu código, Robin precisa interpretar símbolos, prever falhas e reagir a imprevistos durante a tradução de textos milenares.
⚙️ O que é Programação Concorrente?
Concorrência é a capacidade de um programa de executar várias tarefas "ao mesmo tempo", mesmo em ambientes com apenas um núcleo de processamento. Isso é feito por meio do intercalamento inteligente de tarefas (threads, processos ou corrotinas).
Diferente da paralelização (que envolve múltiplas CPUs), a concorrência se foca na organização lógica do programa para que tarefas sejam coordenadas eficientemente, compartilhando tempo de execução.
🧠 Características do Paradigma Concorrente
- Execução intercalada: tarefas podem suspender e retomar sem bloqueio total do programa.
- Gerenciamento de tarefas: envolve múltiplas unidades de execução (threads, processos ou corrotinas).
- Compartilhamento de recursos: exige controle cuidadoso de estado compartilhado.
- Sincronização: evita condições de corrida (race conditions) e inconsistências.
🐍 Como Python executa código em Paradigma Concorrente
Python oferece diversas abordagens para concorrência, cada uma com suas características e limitações, especialmente por conta do GIL (Global Interpreter Lock):
🔸 1. Threading (concorrência com threads)
Usado para I/O concorrente (espera por arquivos, rede etc.), mas limitado em paralelismo real devido ao GIL.
python
import threading
def ler_documento():
print("Lendo documentos do século perdido...")
thread = threading.Thread(target=ler_documento)
thread.start()
thread.join()
🔸 2. Multiprocessing (processos reais)
Cada processo roda em um núcleo separado — ideal para tarefas de CPU.
python
from multiprocessing import Process
def decifrar_rocha():
print("Decifrando rochas lunares...")
p = Process(target=decifrar_rocha)
p.start()
p.join()
🔸 3. Asyncio (corrotinas assíncronas)
Ideal para grande número de tarefas de I/O assíncronas (como múltiplas conexões).
python
import asyncio
async def buscar_fragmento():
print("Buscando fragmento do Poneglyph...")
await asyncio.sleep(1)
print("Fragmento encontrado!")
asyncio.run(buscar_fragmento())
🔄 Gerenciamento de Estado e Escopo em Python Concorrente
🔐 Estado Compartilhado (e seus perigos)
Se várias tarefas acessam e modificam o mesmo dado ao mesmo tempo, o risco de corrupção é alto.
python
import threading
contador = 0
def copiar_simbolo():
global contador
for _ in range(100000):
contador += 1 # Vários "braços" alterando o mesmo dado
threads = [threading.Thread(target=copiar_simbolo) for _ in range(2)]
[t.start() for t in threads]
[t.join() for t in threads]
print(contador) # Resultado inesperado! Fragmentos perdidos.
Esse é um clássico problema de condição de corrida. Para evitar que Robin escreva com todos os braços no mesmo lugar ao mesmo tempo, usamos um "bloqueio" (lock) para que apenas um braço por vez escreva com segurança.
✅ Solução: Uso de locks
python
lock = threading.Lock()
def copiar_com_cuidado():
global contador
for _ in range(100000):
with lock: # Apenas um braço pode escrever por vez
contador += 1
threads = [threading.Thread(target=copiar_com_cuidado) for _ in range(2)]
[t.start() for t in threads]
[t.join() for t in threads]
print(contador) # Agora o total está correto e os símbolos preservados.
Com controle sincronizado, cada tarefa tem sua vez de alterar o estado, como se Robin organizasse uma fila invisível para seus próprios braços — garantindo que nenhum símbolo seja perdido no processo.
📌 Boas Práticas para Programação Concorrente em Python
✅ Use multiprocessing para tarefas intensivas de CPU
✅ Use asyncio para muitas tarefas de I/O não bloqueantes
✅ Evite estado global compartilhado entre threads/processos
✅ Prefira estruturas seguras para concorrência (Queue, Lock, Event)
✅ Divida o problema em tarefas isoladas e independentes
✅ Sempre pense em possíveis condições de corrida
✅ Faça testes em ambientes concorrentes, pois bugs podem ser não determinísticos
🔚 Conclusão
Adotar o paradigma concorrente em Python é um passo estratégico para quem busca construir aplicações escaláveis, responsivas e preparadas para o mundo real. Ao dominar recursos como threads, processos e programação assíncrona, o desenvolvedor supera os limites do GIL e transforma tarefas isoladas em sistemas que cooperam com eficiência.
Assim como Robin, que lê vários idiomas antigos ao mesmo tempo e conecta fragmentos espalhados para formar uma revelação completa, programar de forma concorrente é orquestrar múltiplas ações em harmonia. Cada tarefa, como um braço da Robin, atua em paralelo — e é essa coordenação precisa que permite transformar o caos em clareza, e a lentidão em desempenho.