Módulo 8 - Concorrência com threads

Criando threads com threading

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Criar uma thread com threading.Thread(target=...).
  • Disparar a thread com start e esperar com join.
  • Passar argumentos para a função da thread com args.
  • Ver o ganho de tempo real em uma tarefa de espera.

A sua primeira thread

Criar uma thread no Python é direto. Você importa o módulo threading, cria um objeto Thread apontando para a função que quer rodar em paralelo e chama start. A partir daí, aquela função passa a executar em uma linha de execução separada, enquanto o programa principal continua a sua. O detalhe que confunde iniciantes é a diferença entre chamar a função e passar a função. Você passa a referência da função em target, sem os parênteses, e os argumentos vão à parte, em args, como uma tupla. Se você escrevesse target=tarefa(), estaria chamando a função na hora e passando o resultado dela, não a função em si.

import threading
import time

def baixar(nome):
    print(f"Iniciando {nome}")
    time.sleep(2)  # simula espera de rede
    print(f"Terminou {nome}")

t = threading.Thread(target=baixar, args=("pagina-1",))
t.start()   # dispara a thread
print("Fluxo principal continua...")
t.join()    # espera a thread terminar
print("Tudo pronto")

target recebe a função sem parênteses; args leva os argumentos numa tupla.

start dispara, join espera

Os dois métodos que você mais usa são start e join, e vale gravar bem o papel de cada um. O start agenda a thread para rodar e devolve o controle imediatamente, sem esperar; é por isso que, no exemplo anterior, a mensagem do fluxo principal aparece antes de a tarefa terminar. O join faz o oposto: ele bloqueia quem chamou até a thread terminar de verdade. Sem join, um programa que dispara threads e chega ao fim pode encerrar antes de o trabalho concluir, ou você pode tentar usar um resultado que ainda não existe. A regra prática é: disparou várias threads e precisa dos resultados delas, faça join em todas antes de seguir.

import threading
import time

def tarefa(n):
    time.sleep(1)
    print(f"tarefa {n} concluida")

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

# espera todas terminarem antes de seguir
for t in threads:
    t.join()

print("Todas as 5 tarefas terminaram")

Disparar todas com start, guardar numa lista e dar join em cada uma no fim.

O ganho aparece na espera

Números convencem mais que teoria. Imagine cinco tarefas que esperam um segundo cada, simulando cinco consultas de rede. Feitas em sequência, uma depois da outra, o total é cerca de cinco segundos. Feitas em cinco threads, todas esperam ao mesmo tempo, e o total cai para pouco mais de um segundo. O código abaixo mede os dois jeitos para você ver a diferença com os próprios olhos. Repare que o ganho vem do time.sleep, que representa espera: durante ele, o GIL é liberado e as threads avançam juntas. Se no lugar do sleep houvesse um cálculo pesado, o ganho sumiria.

import threading
import time

def consulta(n):
    time.sleep(1)  # simula I/O

# sequencial
inicio = time.perf_counter()
for i in range(5):
    consulta(i)
print(f"Sequencial: {time.perf_counter() - inicio:.1f}s")

# com threads
inicio = time.perf_counter()
threads = [threading.Thread(target=consulta, args=(i,)) for i in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"Com threads: {time.perf_counter() - inicio:.1f}s")

Sequencial gasta ~5s; com threads, ~1s, porque as esperas se sobrepõem.

Teste rápido

Por que dar start e join dentro do mesmo laço, para cada thread, elimina o ganho de tempo?

Perguntas frequentes

Por que passo a função sem os parênteses em target?
Porque você quer passar a função em si, para a thread chamá-la depois, e não o resultado dela agora. Escrever target=tarefa passa a referência; escrever target=tarefa() chamaria a função imediatamente no fluxo principal e passaria o valor de retorno, o que não é o que você quer.
O que acontece se eu esquecer o join?
O fluxo principal segue sem esperar a thread. Em programas curtos, isso pode fazer o processo terminar antes de a thread concluir, ou você usar um resultado que ainda não ficou pronto. Sempre dê join quando precisar garantir que o trabalho terminou antes de continuar.
Como a thread me devolve um resultado?
Uma função de thread não devolve valor pelo return de forma direta para quem a disparou. Padrões comuns são escrever o resultado em uma estrutura compartilhada, como uma lista ou fila, ou usar o ThreadPoolExecutor, que você vê na última aula deste módulo e já entrega os resultados prontos.
Existe outra forma de criar threads além de target?
Sim. Você pode criar uma subclasse de threading.Thread e sobrescrever o método run com o código da thread. As duas formas são válidas; a de target é mais direta para tarefas simples, e a subclasse organiza melhor quando a thread guarda estado próprio.
A ordem em que as threads terminam é garantida?
Não. Threads são agendadas pelo sistema e podem terminar em qualquer ordem, mesmo que você as dispare em sequência. Nunca escreva código que dependa da ordem de término sem uma coordenação explícita, como join individual ou uma fila. Contar com a ordem é uma fonte comum de bugs.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.