Multithreading em Python: Além do Básico
1. Conceitos Fundamentais
1.1 Threads vs Processos
| Característica | Thread | Processo |
|---|---|---|
| Memória | Compartilhada | Independente |
| Criação | Leve | Pesada |
| GIL | Afeta CPU-bound | Não afeta CPU-bound |
| Uso | I/O-bound | CPU-bound |
Threads são mais eficientes para tarefas de I/O, mas limitadas para computação pura em Python devido ao GIL.
Para tarefas CPU-bound, prefira multiprocessing.
1.2 Criando Threads
import threading
def tarefa(nome):
print(f"Tarefa {nome} iniciada")
threads = []
for i in range(5):
t = threading.Thread(target=tarefa, args=(i,))
t.start()
threads.append(t)
for t in threads:
t.join()
join()garante que o programa espere todas as threads terminarem.
2. Threads com Classes
Usar classes herdando de
threading.Threadpermite maior controle.
class MinhaThread(threading.Thread):
def __init__(self, nome):
super().__init__()
self.nome = nome
def run(self):
print(f"Tarefa {self.nome} executando")
threads = [MinhaThread(i) for i in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
run()é o método que executa quandostart()é chamado.
3. Sincronização de Threads
Quando múltiplas threads compartilham dados, condições de corrida podem ocorrer.
3.1 Locks (Travas)
lock = threading.Lock()
contador = 0
def tarefa():
global contador
for _ in range(1000):
with lock: # protege a seção crítica
contador += 1
threads = [threading.Thread(target=tarefa) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(contador) # sempre 5000
Sem o lock,
contadorpoderia ficar inconsistente.
3.2 RLocks (Reentrant Locks)
Permite uma mesma thread adquirir o lock múltiplas vezes sem deadlock.
rlock = threading.RLock()
4. Thread-safe Queues
queue.Queueé thread-safe e ideal para produtor-consumidor.
import queue
import time
fila = queue.Queue()
def produtor():
for i in range(5):
fila.put(i)
print(f"Produziu {i}")
time.sleep(0.1)
def consumidor():
while True:
item = fila.get()
print(f"Consumiu {item}")
fila.task_done()
if item == 4: break
t1 = threading.Thread(target=produtor)
t2 = threading.Thread(target=consumidor)
t1.start()
t2.start()
t1.join()
t2.join()
Garante sincronização automática entre threads.
5. ThreadPoolExecutor
Gerencia um pool de threads, simplificando criação e controle.
from concurrent.futures import ThreadPoolExecutor
import time
def tarefa(x):
time.sleep(1)
return x**2
with ThreadPoolExecutor(max_workers=5) as executor:
resultados = list(executor.map(tarefa, range(10)))
print(resultados)
Automatiza start, join e gerenciamento de threads.
Útil para I/O-bound ou múltiplas requisições de rede.
6. Estratégias Avançadas
6.1 Evitando GIL para CPU-bound
Python threads não aceleram processamento puro em CPU-bound.
Estratégias:
Usar
multiprocessingem vez de threadsCombinar threads para I/O e processos para CPU
Usar bibliotecas que liberam GIL (
NumPy,Cython,Numba)
6.2 Deadlocks
Evite adquirir múltiplos locks sem ordem consistente.
Use RLocks ou timeout no lock.
if lock.acquire(timeout=2):
try:
# código protegido
finally:
lock.release()
6.3 Comunicação entre threads
Use queues, eventos (
threading.Event) e semáforos (threading.Semaphore).
evento = threading.Event()
def worker():
print("Esperando sinal...")
evento.wait()
print("Sinal recebido!")
t = threading.Thread(target=worker)
t.start()
# Sinaliza após 2s
time.sleep(2)
evento.set()
7. Boas Práticas Profissionais
Prefira ThreadPoolExecutor para simplificar código.
Proteja sempre dados compartilhados com locks ou queues.
Evite threads para cálculos intensivos, use
multiprocessing.Evite deadlocks e garanta ordem consistente de aquisição de locks.
Combine threads com asyncio para I/O moderno.
Monitore threads ativas e logs para detecção de travamentos ou lentidão.
8. Aplicações Profissionais
Web scraping paralelo
Chamada concorrente de APIs
Processamento de arquivos e logs I/O-bound
Sistemas de fila produtor-consumidor
Monitoramento e coleta de dados em tempo real
9. Conclusão
Multithreading em Python é ótimo para I/O-bound, mas limitado em CPU-bound devido ao GIL.
Ferramentas como ThreadPoolExecutor, Locks, RLocks e Queues permitem código seguro e eficiente.
Combinando threads com paralelização de processos ou bibliotecas que liberam o GIL, é possível criar aplicações altamente performáticas e escaláveis.

Comentários
Postar um comentário