Python Programming

Python: Threads e Filas – Dominando Tarefas Concorrentes

Spread the love

Python oferece poderosas capacidades de threading para aprimorar o desempenho de aplicações através da execução concorrente de tarefas. No entanto, o threading não controlado pode levar à contenção de recursos e ineficiência. Este artigo explora técnicas eficazes de threading usando filas em Python, focando na prevenção de armadilhas comuns e na maximização do desempenho.

Sumário

Threads em Python

Threads Python permitem a execução aparentemente simultânea de múltiplas funções. Isso é particularmente vantajoso para operações limitadas por E/S (solicitações de rede, processamento de arquivos), onde uma thread pode esperar por recursos externos sem bloquear outras. No entanto, o Global Interpreter Lock (GIL) em CPython limita o verdadeiro paralelismo para tarefas limitadas pela CPU; apenas uma thread pode controlar o interpretador Python a qualquer momento. Portanto, a eficácia do threading é principalmente realizada com operações limitadas por E/S.

Considere este exemplo de threading simples sem uma fila:


import threading
import time

def worker(name):
    print(f"Thread {name}: iniciando")
    time.sleep(2)  # Simula operação limitada por E/S
    print(f"Thread {name}: finalizando")

threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

print("Todas as threads finalizadas")

Isso cria cinco threads, cada uma executando a função worker. Embora funcional, falta controle sobre a contagem de threads concorrentes, potencialmente sobrecarregando o sistema com inúmeras tarefas.

Gerenciando Threads com Filas

Para controlar a execução concorrente de threads e evitar o esgotamento de recursos, use queue.Queue. A fila atua como um buffer, gerenciando tarefas entre o pool de threads e o processamento. As threads recuperam continuamente tarefas, processando até que a fila esteja vazia. Essa abordagem regula a concorrência e gerencia os recursos de forma eficiente.

Aqui está um exemplo aprimorado utilizando queue.Queue:


import threading
import time
import queue

def worker(q):
    while True:
        try:
            item = q.get(True, 1)  # Bloqueia por 1 segundo, lança exceção se vazio
            print(f"Thread {threading.current_thread().name}: processando {item}")
            time.sleep(2)  # Simula operação limitada por E/S
            print(f"Thread {threading.current_thread().name}: finalizado {item}")
            q.task_done()
        except queue.Empty:
            break

q = queue.Queue()
num_threads = 3  # Controla threads concorrentes
for i in range(10):  # Número de tarefas
    q.put(i)

threads = []
for i in range(num_threads):
    t = threading.Thread(target=worker, args=(q,), daemon=True) # Threads daemon finalizam quando a thread principal finaliza
    threads.append(t)
    t.start()

q.join()  # Aguarda que todos os itens da fila sejam processados

print("Todas as tarefas finalizadas")

Este exemplo usa queue.Queue para manter as tarefas (0-9). Apenas três threads são executadas concorrentemente, puxando da fila. q.join() garante que a thread principal aguarde a conclusão da tarefa. daemon=True faz com que as threads worker sejam encerradas quando a thread principal terminar, evitando travamentos.

Escolhendo a Abordagem Correta: Threads vs. Multiprocessamento

Esta abordagem aprimorada oferece melhor controle, gerenciamento de recursos e escalabilidade. Lembre-se que para tarefas limitadas pela CPU, o multiprocessamento (usando o módulo multiprocessing) geralmente é mais eficiente em CPython do que o threading devido à limitação do GIL. Escolha a abordagem apropriada com base em se suas tarefas são limitadas por E/S ou pela CPU.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *