
Passo 1 de 7
Criar um conjunto de trabalhadores
Entenda o papel do executor e delimite um conjunto de threads reutilizáveis com um limite explícito.
Trilha de aprendizado · Nível 13 · Tutorial 2
Ao concluir, você será capaz de executar funções independentes em um conjunto de threads e recuperar seus resultados ou falhas sem perder a associação com as entradas.
Criar um conjunto de trabalhadores
Entenda o papel do executor e delimite um conjunto de threads reutilizáveis com um limite explícito. 2 min
Submeter uma função e obter seu resultado
Agende uma chamada em um trabalhador e recupere seu retorno por meio de um Future. 3 min
Coletar tarefas conforme ficam prontas
Submeta um conjunto pequeno de tarefas antes de esperar e use as_completed para observar os resultados à medida que cada Future termina. 3 min
Manter cada resultado ligado à sua entrada
Associe cada Future à entrada que originou a tarefa para produzir uma saída correta mesmo quando as conclusões chegam em outra ordem. 2 min
Recuperar falhas sem perder as outras tarefas
Trate falhas de tarefas individuais durante a coleta, mantendo a associação entre cada Future e sua entrada. 3 min
Limitar submissões com lotes finitos
Processe uma fonte de entradas em lotes para limitar quantas tarefas ficam submetidas de uma vez, mantendo a associação entre cada Future e sua entrada. 4 min
Aplicar o fluxo completo e conferir os resultados
Execute um script autocontido que processa entradas em lotes, associa cada conclusão à entrada original e registra sucessos e falhas. 3 min

Passo 1 de 7
Entenda o papel do executor e delimite um conjunto de threads reutilizáveis com um limite explícito.
Um executor recebe chamadas para serem agendadas e as distribui entre trabalhadores disponíveis. Neste tutorial, cada chamada representará uma consulta independente identificada por uma entrada, como um código de produto.
O ThreadPoolExecutor, de concurrent.futures, mantém um conjunto de threads trabalhadoras que pode ser reutilizado para várias tarefas. A tarefa é a chamada a executar; a thread é o trabalhador que a executa. Não são a mesma coisa.
As entradas originam tarefas. O executor organiza essas tarefas para um número limitado de threads trabalhadoras.

As tarefas aguardam ou são executadas pelos trabalhadores do conjunto; uma tarefa não cria necessariamente uma thread própria.
Crie o executor com um valor explícito de max_workers. Esse valor define o máximo de threads trabalhadoras: não significa que todas serão criadas imediatamente nem que estarão sempre ocupadas.
Use with para delimitar o ciclo de vida do executor. Na saída normal do bloco, o contexto espera as tarefas já submetidas terminarem antes de continuar. Vamos aprender a submeter essas tarefas no próximo passo.
Este é o ponto de partida do cenário de consultas simuladas.
from concurrent.futures import ThreadPoolExecutor
import time
def consultar(codigo: str) -> str:
time.sleep(0.2) # simula uma espera bloqueante curta
return f"consulta concluída: {codigo}"
codigos = ["A10", "B20", "C30"]
with ThreadPoolExecutor(max_workers=3) as executor:
# As chamadas serão agendadas aqui no próximo passo.
pass
print("Executor encerrado")Você precisa de um executor com, no máximo, 2 threads trabalhadoras. Qual opção atende ao requisito?
Verdadeiro ou falso: ao sair normalmente do bloco with ThreadPoolExecutor(...), o programa pode continuar enquanto tarefas já submetidas ainda estão em execução.
Resumo
Você já pode preparar o ambiente para executar consultas independentes.
ThreadPoolExecutor organiza chamadas para um conjunto reutilizável de threads.max_workers é o limite máximo de trabalhadores, não uma garantia de ocupação imediata.with delimita o ciclo de vida e, na saída normal, espera as tarefas submetidas terminarem.
Passo 2 de 7
Agende uma chamada em um trabalhador e recupere seu retorno por meio de um Future.
executor.submit recebe a função e seus argumentos separadamente. Ele agenda a chamada para um trabalhador e devolve imediatamente um objeto Future.
Use executor.submit(consultar, "item-42"), e não executor.submit(consultar("item-42")). Na segunda forma, consultar(...) é executada antes de submit, na thread que está fazendo a submissão.
A chamada é agendada primeiro; a função roda no trabalhador; o retorno só é recuperado quando o programa pede o resultado.

submit devolve um Future; result() recupera o retorno da função quando ele estiver disponível.
O valor retornado por submit é um Future de concurrent.futures, não o retorno de consultar.
Um Future representa uma execução que pode estar pendente, em andamento ou já concluída quando você a observa. Para obter o retorno da função, chame future.result().
Dica
Se a função ainda estiver rodando, future.result() bloqueia a thread que fez essa chamada até haver um retorno disponível. Se o trabalho já terminou, ele devolve o retorno imediatamente.
Crie um arquivo chamado future_simples.py, copie o código e execute python future_simples.py no seu terminal. A pausa é apenas uma simulação local de uma operação que demora um pouco.
from concurrent.futures import ThreadPoolExecutor
import time
def consultar(identificador: str) -> str:
print(f"Trabalhador: consultando {identificador}")
time.sleep(1)
return f"resultado de {identificador}"
with ThreadPoolExecutor(max_workers=2) as executor:
future = executor.submit(consultar, "item-42")
print(f"Recebi um Future: {future}")
resultado = future.result()
print(f"Recebi o resultado: {resultado}")Exemplo
A primeira mensagem do programa mostra uma representação de Future, com detalhes que podem variar. A última mensagem mostra o texto retornado pela função: resultado de item-42.
A ordem exata das duas mensagens iniciais pode variar, pois programa principal e trabalhador são threads diferentes. O ponto importante é: a variável future não contém o texto de retorno; resultado contém, após future.result().
Para agendar consultar com o identificador "item-42", complete:
future = executor.submit(_____, "item-42")
Depois de executar o script, explique a diferença observada entre o valor impresso para future e o valor impresso para resultado. Mencione qual linha pode esperar pela conclusão da consulta.
Escreva pelo menos 60 caracteres (0/60).

Passo 3 de 7
Submeta um conjunto pequeno de tarefas antes de esperar e use as_completed para observar os resultados à medida que cada Future termina.
Para permitir sobreposição, faça duas fases distintas:
submit para todas as tarefas de um conjunto pequeno e finito, guardando os Futures.Futures quando eles forem concluídos.Se você chamar future.result() logo após cada submit, a thread principal espera aquela tarefa terminar antes de submeter a próxima. Assim, o conjunto de trabalhadores não recebe várias tarefas para coordenar.

Esperar após cada submissão serializa esse fluxo; submeter antes de coletar permite que os trabalhadores avancem em tarefas independentes.
as_completed(futures) recebe uma coleção de Futures e produz cada Future assim que ele fica concluído. Ele não entrega diretamente o valor retornado pela função: ainda é necessário chamar future.result().
A ordem produzida não é a ordem de submissão. Percorrer uma lista de futuros na ordem original pode fazer você esperar pelo primeiro, mesmo que os seguintes já tenham terminado. Com as_completed, você observa primeiro o que estiver disponível.
Salve como coletar.py e execute com python coletar.py. As pausas apenas simulam tarefas independentes; a sequência exibida pode variar.
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def consultar(nome: str, demora: float) -> str:
time.sleep(demora)
return f"consulta concluída: {nome}"
consultas = [
("produto-A", 0.30),
("produto-B", 0.10),
("produto-C", 0.20),
]
with ThreadPoolExecutor(max_workers=3) as executor:
# Fase 1: todas as chamadas são submetidas antes da coleta.
futuros = [
executor.submit(consultar, nome, demora)
for nome, demora in consultas
]
# Fase 2: cada Future é entregue quando fica concluído.
for futuro in as_completed(futuros):
resultado = futuro.result()
print(resultado)Neste exemplo, é comum que produto-B apareça antes porque sua pausa é menor. Porém, não use isso como regra do programa: o agendamento depende do ambiente e dois ou mais futuros podem já estar concluídos quando a coleta começar.
Portanto, trate a ordem de as_completed como ordem de disponibilidade, não como uma sequência fixa para comparar em testes ou para combinar posições de listas.
No trecho for futuro in as_completed(futuros): resultado = futuro.result(), o que futuro.result() faz?
Explique por que este padrão não permite a sobreposição pretendida entre três tarefas:
for nome, demora in consultas:
futuro = executor.submit(consultar, nome, demora)
print(futuro.result())Indique também a mudança geral necessária.
Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 7
Associe cada Future à entrada que originou a tarefa para produzir uma saída correta mesmo quando as conclusões chegam em outra ordem.
as_completed entrega os futuros na ordem em que terminam, não na ordem em que foram submetidos. Por isso, associar a primeira conclusão à primeira entrada é frágil: uma tarefa posterior pode terminar antes.
Ao submeter cada tarefa, guarde o Future como chave de um dicionário e a entrada correspondente como valor. Quando esse mesmo Future aparecer na coleta, ele permitirá recuperar sua origem.
Cada seta de retorno passa pelo mesmo Future criado na submissão.

A ordem de conclusão muda; o vínculo Future → entrada não.
A compreensão cria uma associação para cada chamada submetida.
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def consultar(entrada: str, demora: float) -> str:
time.sleep(demora)
return f"resposta para {entrada}"
entradas = [
("produto-A", 0.3),
("produto-B", 0.1),
("produto-C", 0.2),
]
with ThreadPoolExecutor(max_workers=3) as executor:
futuro_para_entrada = {
executor.submit(consultar, entrada, demora): entrada
for entrada, demora in entradas
}
for futuro in as_completed(futuro_para_entrada):
entrada = futuro_para_entrada[futuro]
resultado = futuro.result()
print(f"{entrada}: {resultado}")Dica
Na compreensão, submit(...) produz a chave Future; entrada é o valor guardado. No laço, futuro_para_entrada[futuro] recupera a entrada antes de futuro.result() obter o retorno.
Neste cenário, a ordem provável de conclusão é produto-B, produto-C, produto-A, porque as esperas são diferentes. A ordem exata não deve ser tratada como contrato, mas cada linha continuará correta porque o coletor consulta a entrada pelo Future concluído.
Evite emparelhar uma lista de entradas com resultados que chegam por as_completed: as posições pertencem a ordens diferentes.
Considere que as tarefas foram submetidas na ordem A, B, C, mas as_completed as entregou na ordem B, C, A. Relacione cada Future concluído à entrada correta.
Toque em um item e depois no par correspondente.

Passo 5 de 7
Trate falhas de tarefas individuais durante a coleta, mantendo a associação entre cada Future e sua entrada.
Um Future concluído pode guardar um retorno ou uma exceção. Ao chamar future.result(), o retorno é entregue normalmente; se a função falhou, a mesma exceção é propagada no ponto dessa chamada.
Por isso, ao coletar com as_completed, coloque o result() dentro de try e except. O mapeamento Future → entrada permite informar exatamente qual entrada falhou, mesmo sem retorno da função.
A coleta identifica a entrada pelo Future antes de recuperar seu resultado.

Cada Future concluído pode levar a um resultado ou a uma exceção; o dicionário preserva a ligação com a entrada.
Atenção
Se result() lançar uma exceção sem tratamento dentro do laço, a coleta é interrompida naquele ponto. As outras funções já submetidas não são paradas automaticamente, mas seus resultados ou falhas podem deixar de ser observados pelo código.
Crie um arquivo, por exemplo coletar_falhas.py, cole o código e execute com python coletar_falhas.py. A entrada "produto-inexistente" foi programada para falhar; as demais devem produzir resultados.
O except ValueError fica ao redor de future.result(), que é o ponto onde a falha do trabalhador aparece no chamador.
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def consultar(entrada: str) -> str:
time.sleep(0.2)
if entrada == "produto-inexistente":
raise ValueError("produto não encontrado")
return f"dados de {entrada}"
entradas = ["produto-10", "produto-inexistente", "produto-30"]
with ThreadPoolExecutor(max_workers=2) as executor:
futuros_para_entrada = {
executor.submit(consultar, entrada): entrada
for entrada in entradas
}
for future in as_completed(futuros_para_entrada):
entrada = futuros_para_entrada[future]
try:
resultado = future.result()
except ValueError as erro:
print(f"FALHA em {entrada}: {erro}")
else:
print(f"SUCESSO em {entrada}: {resultado}")Dica
A ordem das linhas não é garantida: as_completed entrega cada Future quando ele termina. O importante é que cada linha usa a entrada recuperada pelo próprio Future, e não uma posição da lista.
Se você remover o try e o except, future.result() propagará a exceção para fora do bloco with. Antes de a exceção continuar para o chamador, a saída do contexto ainda espera as tarefas já submetidas terminarem.
Essa espera não recupera automaticamente as exceções guardadas nos outros Futures. Para relatar sucesso ou falha de cada tarefa, recupere cada Future durante a coleta.
Se future.result() propagar uma exceção para fora do with, as outras tarefas já submetidas são automaticamente interrompidas.
Após executar o script, qual entrada provocou a falha controlada? O que aconteceu com as outras entradas? Explique também a diferença entre a exceção em result() e a espera na saída do with.
Escreva pelo menos 60 caracteres (0/60).

Passo 6 de 7
Processe uma fonte de entradas em lotes para limitar quantas tarefas ficam submetidas de uma vez, mantendo a associação entre cada Future e sua entrada.
max_workers limita quantas funções podem executar ao mesmo tempo. Ele não limita quantas chamadas você pode enviar com submit e deixar aguardando no executor.
Por exemplo, com max_workers=2, é possível submeter 10.000 tarefas: apenas até duas executam simultaneamente, mas as demais podem ficar pendentes. Para limitar também as tarefas submetidas, processe a fonte em lotes finitos.
Compare os dois fluxos.

À esquerda, poucos trabalhadores e muitas submissões acumuladas. À direita, o lote atual é coletado antes de enviar o próximo.
Dica
Com lote de tamanho B, há no máximo B tarefas submetidas pertencentes ao lote atual. Já a execução simultânea continua limitada por max_workers.
Crie o iterador da fonte uma única vez. Em cada repetição, islice consome dele apenas os próximos itens do lote. Submeta somente esse lote, crie o mapeamento Future → entrada e colete todas as conclusões com as_completed antes de buscar novas entradas.
O mesmo executor permanece ativo durante todo o processamento, pois o with envolve o laço de lotes.
Execute este script localmente. O último lote tem apenas um item, e item-5 falha de forma controlada.
from concurrent.futures import ThreadPoolExecutor, as_completed
from itertools import islice
from time import sleep
def consultar(entrada: str) -> str:
sleep(0.2)
if entrada == "item-5":
raise ValueError("entrada indisponível")
return f"resultado de {entrada}"
def processar_em_lotes(entradas: list[str], tamanho_lote: int) -> None:
iterador = iter(entradas)
with ThreadPoolExecutor(max_workers=2) as executor:
while True:
lote = list(islice(iterador, tamanho_lote))
if not lote:
break
futuro_para_entrada = {
executor.submit(consultar, entrada): entrada
for entrada in lote
}
for futuro in as_completed(futuro_para_entrada):
entrada = futuro_para_entrada[futuro]
try:
resultado = futuro.result()
print(f"SUCESSO | {entrada}: {resultado}")
except ValueError as erro:
print(f"FALHA | {entrada}: {erro}")
processar_em_lotes(
["item-1", "item-2", "item-3", "item-4", "item-5"],
tamanho_lote=2,
)Coloque as etapas de processamento de um lote na ordem correta.
Ao terminar o for futuro in as_completed(...), a variável futuro_para_entrada será substituída no próximo ciclo; assim, o programa não precisa manter os Futures de lotes anteriores.
Isso não impede que você acumule resultados em outra lista ou estrutura. Se fizer isso, esse histórico continuará crescendo conforme sua escolha.
Há uma troca: como o código espera o lote inteiro, um trabalhador pode ficar ocioso perto do fim se restar apenas uma tarefa lenta. Ainda assim, o padrão é simples e dá um limite claro às submissões.
Em suas palavras, qual é a diferença entre max_workers=2 e tamanho_lote=2? Explique também por que o próximo lote só é consumido após a coleta completa do atual.
Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7
Execute um script autocontido que processa entradas em lotes, associa cada conclusão à entrada original e registra sucessos e falhas.
Neste fluxo, um único ThreadPoolExecutor atende toda a fonte, mas cada lote é totalmente coletado antes do próximo ser submetido. Para cada lote, o programa cria o mapeamento Future → entrada, usa as_completed para receber conclusões e chama result() para obter o retorno ou a exceção.
A ordem das linhas pode variar. O critério de correção é outro: cada entrada deve aparecer uma vez, como sucesso ou falha.
O diagrama mostra onde está o limite de trabalhadores e onde está o limite de submissões pendentes.

max_workers limita threads em execução; TAMANHO_LOTE limita quantas tarefas são submetidas por vez.
Dica
Altere MAX_WORKERS para mudar o máximo de threads trabalhadoras. Altere TAMANHO_LOTE para mudar o máximo de tarefas submetidas ainda não concluídas em cada rodada. Um valor não substitui o outro.
Crie um arquivo chamado lotes_threads.py, copie o código completo abaixo e execute no terminal com:
python lotes_threads.py
Ele usa apenas a biblioteca padrão. A falha de pedido-5 é intencional.
Script autocontido: processa sete entradas em lotes de três, com uma falha controlada.
from concurrent.futures import ThreadPoolExecutor, as_completed
from itertools import islice
from time import sleep
MAX_WORKERS = 3
TAMANHO_LOTE = 3
def consultar(entrada: str) -> str:
sleep(0.2) # simula uma operação bloqueante curta
if entrada == "pedido-5":
raise ValueError("pedido inválido")
return f"resposta para {entrada}"
def processar_em_lotes(entradas: list[str]) -> None:
fonte = iter(entradas)
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
while True:
lote = list(islice(fonte, TAMANHO_LOTE))
if not lote:
break
future_para_entrada = {
executor.submit(consultar, entrada): entrada
for entrada in lote
}
for future in as_completed(future_para_entrada):
entrada = future_para_entrada[future]
try:
resultado = future.result()
except ValueError as erro:
print(f"FALHA | {entrada}: {erro}")
else:
print(f"SUCESSO | {entrada}: {resultado}")
entradas = [
"pedido-1",
"pedido-2",
"pedido-3",
"pedido-4",
"pedido-5",
"pedido-6",
"pedido-7",
]
processar_em_lotes(entradas)Você deve encontrar sete linhas no total: seis iniciadas por SUCESSO e uma iniciada por FALHA, identificando pedido-5. As linhas podem surgir em ordem diferente da lista de entradas.
Com lote de tamanho 3 e sete entradas, o programa processa lotes de 3, 3 e 1 item. Portanto, o último lote incompleto também é submetido e coletado.
Após executar, informe: os valores de MAX_WORKERS e TAMANHO_LOTE; o total de sucessos e falhas; a entrada que falhou; e por que o código mantém a associação correta e não submete toda a fonte de uma vez.
Escreva pelo menos 120 caracteres (0/120).
Resumo
Use este fluxo quando houver chamadas independentes e você precisar acompanhar cada uma sem acumular trabalho pendente para toda a fonte.
ThreadPoolExecutor com max_workers explícito e mantenha-o em um bloco with.submit; guarde o Future retornado.Future à entrada em um dicionário antes da coleta.as_completed para reagir às conclusões, chamando future.result() dentro de try/except para registrar sucessos e falhas.Parabéns! Você concluiu: Executar tarefas com ThreadPoolExecutor
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks