Wilberhg's blog

Escrita Concorrente Segura em Excel com Openpyxl e ThreadPoolExecutor

Entendendo o Conceito

O gerenciamento manual do ciclo de vida das threads (como criação, ativação e junção) pode se tornar complexo e propenso a erros à medida que o número de tarefas concorrentes cresce. A classe ThreadPoolExecutor, integrada ao módulo concurrent.futures, simplifica essa administração ao fornecer um pool de threads trabalhadoras pré-configuradas para executar tarefas de entrada e saída (I/O) de forma assíncrona. Essa abordagem otimiza o uso de recursos e simplifica a estrutura do código concorrente.

Contudo, o uso de um executor em pool não elimina a necessidade de controle de concorrência nos objetos de planilha compartilhados. Como o openpyxl continua sendo não seguro para threads (not thread-safe), as requisições enviadas ao pool de threads podem corromper as estruturas internas do arquivo Excel se acessarem o mesmo objeto simultaneamente. Por essa razão, a sincronização utilizando travas de exclusão mútua (locks) continua obrigatória para proteger o bloco crítico de gravação e gravação física do arquivo em disco.

Passo a Passo

O processo de implementação envolve configurar um pool de threads que delega tarefas para uma classe gerenciadora equipada com travas de segurança.

Primeiramente, certifique-se de instalar a biblioteca openpyxl:

pip install openpyxl

Em seguida, implemente o script contendo a classe protetora e o uso do gerenciador de contexto ThreadPoolExecutor:

import threading
from concurrent.futures import ThreadPoolExecutor
from openpyxl import Workbook

class ThreadSafeExcelWriter:
    def __init__(self, filename):
        self.filename = filename
        self.wb = Workbook()
        self.ws = self.wb.active
        self.lock = threading.Lock()

    def write_data(self, row, col, value):
        with self.lock:
            self.ws.cell(row=row, column=col, value=value)

    def save_workbook(self):
        with self.lock:
            self.wb.save(self.filename)

def worker_task(writer, start_row, value):
    for i in range(5):
        writer.write_data(row=start_row + i, col=1, value=value)

writer = ThreadSafeExcelWriter("dados_concorrentes.xlsx")

with ThreadPoolExecutor(max_workers=4) as executor:
    futures = [
        executor.submit(worker_task, writer, index * 10, f"Valor-{index}")
        for index in range(1, 6)
    ]

writer.save_workbook()

Explicando o código

A rotina de execução estruturada implementa conceitos específicos para o tratamento de chamadas paralelas:

Observações

Referências

  1. Documentação de Concorrência e ThreadPoolExecutor no Python: https://docs.python.org/3/library/concurrent.futures.html
  2. Práticas Recomendadas do ThreadPoolExecutor no SuperFastPython: https://superfastpython.com/threadpoolexecutor-best-practices/
  3. Guia de Thread Safety e Sincronização da Real Python: https://realpython.com/python-thread-lock/
  4. Controle e Manipulação do openpyxl de Modo Multithread: https://stackoverflow.com/questions/11234203/openpyxl-thread-safe

#concurrency #openpyxl #python #threadpoolexecutor #threadsafe