Trilha de aprendizado · Nível 13 · Tutorial 1

Escolher entre threads, processos e asyncio

Ao concluir, você será capaz de escolher um modelo de concorrência conforme o tipo de tarefa, as necessidades de compartilhamento de dados e as características do interpretador.

  • Nível: Intermediário
  • Duração: 15 min
  • 8 passos
Escolher entre threads, processos e asyncio

O que você vai percorrer

  1. Concorrência não é o mesmo que paralelismo Leia linhas do tempo para diferenciar sequência, concorrência e paralelismo. 2 min
  2. Identificar o que limita a tarefa Classifique tarefas pela etapa que mais domina sua duração: espera por recursos, processamento ou uma combinação dos dois. 2 min
  3. Threads: execução com memória compartilhada Entenda o que threads compartilham dentro de um processo e por que elas são uma opção para coordenar esperas de entrada e saída. 2 min
  4. Processos: isolamento e comunicação explícita Entenda como espaços de memória separados afetam o paralelismo, os dados e os custos de usar processos. 2 min
  5. Asyncio: tarefas que cooperam no laço de eventos Entenda como tarefas cooperam no laço de eventos e reconheça o que impede esse progresso. 2 min
  6. Considerar o GIL e a configuração do interpretador Entenda como o GIL e a configuração efetiva do interpretador afetam as expectativas sobre threads. 2 min
  7. Transformar os critérios em uma escolha Use uma sequência de decisão para selecionar uma estratégia inicial — ou decidir manter a execução sequencial — considerando gargalo, bibliotecas, estado e custos. 2 min
  8. Aplicação final: justificar e revisar a decisão Aplique os critérios de escolha a cenários concretos, explique os custos envolvidos e identifique quando uma decisão deve ser revista. 2 min

O que você vai aprender

  • Distinguir progresso concorrente de execução paralela.
  • Classificar tarefas como predominantemente limitadas por entrada e saída ou por processamento.
  • Comparar threads, processos e asyncio quanto a execução, isolamento e compartilhamento de estado.
  • Justificar uma escolha considerando o GIL quando habilitado no CPython, sem presumir ganhos automáticos de velocidade.

Antes de começar

  • Decompor um programa em funções com responsabilidades claras
  • Controlar referências e cópias de coleções

Passo 1 de 8

Concorrência não é o mesmo que paralelismo

Leia linhas do tempo para diferenciar sequência, concorrência e paralelismo.

Três formas de organizar tarefas

Sequência, concorrência e paralelismo

Em uma execução sequencial, a tarefa B só começa quando a tarefa A termina.

Há concorrência quando mais de uma tarefa faz progresso em períodos que se sobrepõem. Isso pode ocorrer por alternância: enquanto A aguarda algo, B executa.

Há paralelismo quando duas tarefas estão efetivamente executando ao mesmo tempo.

Linhas do tempo comparadas

Observe que a sobreposição no tempo não significa, por si só, dois trechos executando no mesmo instante.

Diagrama com três linhas do tempo: execução sequencial com A seguida de B; concorrência com A executando, aguardando e B executando durante a espera; paralelismo com A e B executando simultaneamente em faixas separadas.

Azul e laranja representam tarefas; cinza representa espera. Só o último caso mostra execução simultânea.

O que a linha do tempo revela

Exemplo

Duas tarefas, dois estados

Imagine duas tarefas: A consulta um serviço e B prepara um relatório.

  • Se A executa, termina, e só então B começa: é sequencial.
  • Se A inicia uma consulta, fica aguardando a resposta e B avança nesse intervalo: há concorrência, mesmo que apenas uma tarefa execute por vez.
  • Se A e B têm trechos de trabalho em execução no mesmo instante: há paralelismo.

Uma tarefa aguardando está pendente, mas não está executando processamento naquele momento.

Dica

Não conclua velocidade só pela sobreposição

A concorrência pode aproveitar períodos de espera, mas não garante que o tempo total será menor. Há custos de coordenação, e a tarefa pode não ter espera útil para sobrepor.

Identifique o cenário

Concorrência sem paralelismo

Qual linha do tempo representa concorrência, mas não paralelismo?

Passo 2 de 8

Identificar o que limita a tarefa

Classifique tarefas pela etapa que mais domina sua duração: espera por recursos, processamento ou uma combinação dos dois.

O que ocupa a maior parte do tempo?

O gargalo predominante

Uma tarefa é limitada por entrada e saída (I/O-bound) quando passa a maior parte do tempo aguardando recursos externos, como rede, banco de dados, disco ou outro serviço.

Ela é limitada por processamento (CPU-bound) quando a maior parte do tempo é consumida executando cálculos no processador.

A classificação depende do que domina a duração total, e não apenas de existir uma leitura de arquivo ou um laço no código.

Espera e processamento

Compare a distribuição de tempo nas duas situações.

Diagrama comparando uma tarefa I/O-bound, quase toda em espera de rede e disco, com uma tarefa CPU-bound, quase toda em cálculo ativo no processador.

Em I/O-bound, a espera domina; em CPU-bound, o cálculo domina.

Leia a distribuição, não uma palavra isolada

Exemplo

Dois casos

Caso A — consulta remota: para cada pedido, o programa usa 50 ms preparando dados, espera 1,8 s pela resposta de um serviço e usa 30 ms para tratar o retorno. É predominantemente I/O-bound: a espera pela rede domina.

Caso B — análise de imagem local: o programa lê uma imagem em 40 ms e executa 4 s de cálculos sobre seus pixels. É predominantemente CPU-bound: há leitura, mas o cálculo domina.

Dica

Pergunta útil

Pergunte: “Se eu reduzisse a etapa mais demorada, a duração total cairia bastante?” Essa etapa é a principal evidência para a classificação.

Uma tarefa pode ser mista

Olhe para as etapas

Muitas cargas combinam espera e cálculo. Por exemplo, baixar arquivos, decodificá-los e gerar miniaturas envolve rede, armazenamento e processamento.

Nesses casos, não force um único rótulo para o programa inteiro. Descreva as etapas e identifique qual delas tem maior peso no cenário analisado.

Carga com etapas diferentes

Uma mesma carga pode alternar entre espera e trabalho ativo.

Fluxo em três etapas: espera por download, processamento intenso de dados e gravação de resultado, com durações visuais diferentes.

A classificação pode mudar se a duração relativa das etapas mudar.

Pratique a classificação

Associe cada carga à classificação

Relacione cada descrição à classificação mais adequada.

Toque em um item e depois no par correspondente.

Passo 3 de 8

Threads: execução com memória compartilhada

Entenda o que threads compartilham dentro de um processo e por que elas são uma opção para coordenar esperas de entrada e saída.

Vários fluxos, um processo

Thread: um fluxo dentro do processo

Uma thread é um fluxo de execução dentro de um processo. Um mesmo processo pode ter mais de uma thread avançando em tarefas diferentes ao longo do tempo.

A consequência central é que as threads do processo usam o mesmo espaço de memória. Portanto, elas podem acessar os mesmos objetos Python — por exemplo, a mesma lista, dicionário ou instância.

Memória compartilhada

Observe que os fluxos são separados, mas os objetos pertencem ao mesmo processo.

Diagrama de um processo contendo três threads conectadas à mesma área de memória, onde há uma lista e um dicionário compartilhados.

Threads distintas podem acessar os mesmos objetos mantidos na memória do processo.

Troca simples; alteração exige cuidado

Exemplo

O mesmo objeto para todas as threads

Se uma parte do programa mantém este objeto:

resultados = []

As threads desse processo podem receber uma referência para resultados e consultar ou alterar essa mesma lista. Não surge automaticamente uma lista independente para cada thread.

Isso facilita trocar dados sem transportá-los entre memórias separadas. Porém, se mais de uma thread altera um objeto compartilhado, o programa precisa coordenar esses acessos e a biblioteca usada deve ser compatível com esse uso. Esse controle será estudado adiante.

Dica

Não confunda compartilhamento com segurança

Memória compartilhada torna o acesso direto possível; ela não garante que alterações simultâneas preservem as regras do seu programa.

Esperas de I/O podem se sobrepor

Enquanto uma espera, outra pode avançar

Threads são candidatas úteis para coordenar chamadas bloqueantes de bibliotecas síncronas quando a carga é predominantemente de entrada e saída, como rede ou armazenamento.

Enquanto uma thread aguarda uma resposta, outra pode avançar. Isso depende do comportamento da operação e da biblioteca: criar threads, por si só, não transforma qualquer chamada em concorrente nem garante redução do tempo total.

Aproveitando a espera

Uma thread fica aguardando uma resposta de rede; outra continua seu trecho de trabalho no mesmo processo.

Linha do tempo com duas threads no mesmo processo: a primeira está em espera de rede enquanto a segunda executa uma tarefa, ambas conectadas à memória compartilhada.

A espera de I/O de uma thread pode abrir espaço para o progresso de outra.

Verifique a ideia central

Memória das threads

Verdadeiro ou falso: quando um processo cria várias threads, cada thread recebe automaticamente uma cópia independente de toda lista que já existia no processo.

Progresso durante I/O

Verdadeiro ou falso: uma thread aguardando uma operação de rede pode permitir que outra thread do mesmo processo avance.

Passo 4 de 8

Processos: isolamento e comunicação explícita

Entenda como espaços de memória separados afetam o paralelismo, os dados e os custos de usar processos.

Memórias separadas

Um processo não enxerga automaticamente o outro

Um processo é uma unidade de execução com seu próprio espaço de memória. Diferentemente das threads de um mesmo processo, processos distintos não acessam diretamente os mesmos objetos Python.

Se dois processos começam a partir de dados parecidos, cada um trabalha sobre sua própria cópia lógica daqueles dados. Alterar uma lista em um processo não atualiza automaticamente a lista do outro.

Isolamento de estado

Observe que a mudança feita pelo Processo A permanece no espaço de memória dele.

Diagrama de dois processos em caixas separadas. Cada caixa possui uma lista própria; o Processo A altera sua lista e a lista do Processo B permanece inalterada.

Objetos de processos diferentes ficam em espaços de memória separados.

Exemplo

Consequência prática

Imagine que o processo principal entregue uma coleção de pedidos a outro processo para cálculo. O trabalhador produz resultados no espaço dele; para o processo principal usá-los, esses resultados precisam voltar por algum meio de comunicação explícito. Não basta o trabalhador alterar uma variável local esperando que ela mude no processo principal.

Paralelismo possível, dados em trânsito

Por que considerar processos?

Processos podem executar trabalho ao mesmo tempo em diferentes núcleos de CPU, se houver núcleos e recursos disponíveis. Isso os torna uma alternativa a avaliar para tarefas de processamento intensivo e suficientemente independentes.

Mas o isolamento tem um preço: entradas precisam ser enviadas ao trabalhador, e resultados precisam ser trazidos de volta. Essa troca não acontece por memória compartilhada automática.

Fluxo de entradas e resultados

O processo principal distribui partes independentes do trabalho e recebe os resultados para reunir a resposta final.

Diagrama de um processo principal enviando três conjuntos de dados para processos trabalhadores isolados em núcleos diferentes e recebendo três resultados de volta.

A comunicação transporta dados entre memórias separadas.

Avalie o custo da separação

Isolamento é uma troca

Criar processos consome tempo e memória. Além disso, transportar dados tem custo. Se cada tarefa recebe ou devolve um volume grande de informações, a comunicação pode consumir parte importante do benefício esperado do paralelismo.

Por isso, processos tendem a fazer mais sentido quando o cálculo de cada unidade de trabalho é relevante em comparação com os dados que precisam circular. Se as tarefas dependem intensamente de um estado compartilhado ou exigem trocas frequentes, a coordenação pode ficar cara.

Dica

Pergunta de decisão

Antes de escolher processos, pergunte: as partes do trabalho são independentes? Há processamento suficiente em cada parte? Quanto dado precisará ir e voltar entre os processos?

Verifique a escolha

Cenário de processamento

Um programa separa 1.000 imagens em lotes e envia cada lote a processos para aplicar um cálculo pesado. Ao fim, o processo principal precisa reunir os resultados. Qual afirmação é mais adequada?

Passo 5 de 8

Asyncio: tarefas que cooperam no laço de eventos

Entenda como tarefas cooperam no laço de eventos e reconheça o que impede esse progresso.

Um laço, várias tarefas cooperando

Coordenação cooperativa

O asyncio coordena tarefas por meio de um laço de eventos, normalmente executado em uma única thread. Por isso, ele pode produzir concorrência — avanço alternado de tarefas — sem criar paralelismo de processamento por conta própria.

Quando uma tarefa chega a uma espera compatível com asyncio, ela pode se suspender e devolver o controle ao laço. O laço então deixa outra tarefa pronta avançar. Isso é especialmente útil quando há muitas esperas de entrada e saída, como rede.

Espera que libera o laço

Observe que a espera de uma tarefa abre espaço para outra, mas apenas uma executa código na thread do laço em cada instante.

Diagrama de linha do tempo com um único laço de eventos alternando entre tarefa A e tarefa B: A inicia uma espera de rede, B executa enquanto A aguarda, e A retoma depois.

Na espera compatível, a tarefa suspensa cede o controle; isso permite progresso concorrente, não processamento simultâneo.

Cooperar depende da operação

Exemplo

Duas situações diferentes

Espera cooperativa: uma tarefa usa uma API assíncrona compatível para aguardar uma resposta de rede. Enquanto a resposta não chega, o laço pode avançar outras tarefas prontas.

Bloqueio do laço: uma tarefa chama uma função síncrona demorada — por exemplo, uma leitura bloqueante ou um cálculo longo. Essa chamada ocupa a única thread do laço até terminar. Nesse intervalo, as outras tarefas do mesmo laço não avançam.

Colocar uma chamada síncrona dentro de uma tarefa não a transforma em assíncrona. Para aproveitar asyncio diretamente, a biblioteca usada precisa oferecer uma API assíncrona compatível.

Atenção

Uma thread não resolve o estado compartilhado

As tarefas do mesmo laço podem acessar os mesmos objetos do processo. Mesmo sem paralelismo entre elas, uma tarefa pode observar um estado, suspender durante uma espera e encontrar esse estado alterado ao retomar. Regras que dependem de várias etapas ainda exigem coordenação apropriada, assunto que será aprofundado adiante.

Qual linha do tempo libera o laço?

Identifique a espera cooperativa

Qual diagrama representa uma tarefa aguardando de modo compatível com asyncio, permitindo que outra tarefa do mesmo laço avance?

Passo 6 de 8

Considerar o GIL e a configuração do interpretador

Entenda como o GIL e a configuração efetiva do interpretador afetam as expectativas sobre threads.

O que o GIL limita

Uma thread de código Python por vez

No CPython com o GIL habilitado, o GIL é um bloqueio do interpretador que restringe a execução de código Python a uma thread por vez dentro do mesmo interpretador.

Isso limita o paralelismo de processamento de código Python entre threads. Ainda assim, várias threads podem fazer progresso concorrente: enquanto uma aguarda uma operação de entrada e saída, outra pode avançar.

Concorrência não implica processamento paralelo

Observe a diferença entre alternar o acesso ao interpretador e executar em processos separados.

Diagrama comparando duas threads de um mesmo processo que se alternam para executar código Python por uma única passagem do interpretador, com dois processos separados executando em núcleos distintos.

Com GIL habilitado, threads do mesmo interpretador podem alternar progresso; processos têm interpretadores e espaços de memória separados.

Verifique a ideia

Com o GIL habilitado no CPython, usar várias threads torna automaticamente paralelo o processamento de código Python puro.

Não transforme o GIL em regra absoluta

O código executado também importa

Muitas operações de entrada e saída liberam o GIL durante a espera. Algumas extensões nativas também podem liberá-lo e realizar trabalho fora do código Python puro. Portanto, dizer que threads “nunca” executam processamento em paralelo é uma simplificação incorreta.

A expectativa correta depende da operação, da biblioteca e da configuração em uso.

Atenção

O GIL não protege a regra de negócio

O GIL não garante que alterações em objetos compartilhados sejam seguras para a sua aplicação. Uma regra que envolve ler, decidir e alterar estado ainda pode depender da intercalação das threads. A ausência do GIL também não remove a necessidade de coordenação.

Escolha a conclusão mais precisa

Uma aplicação usa threads para fazer várias chamadas de rede por uma biblioteca síncrona adequada ao uso concorrente. Qual conclusão é mais adequada no CPython com GIL habilitado?

Processos e configurações sem GIL

Separe as perguntas

Processos separados não disputam um único GIL do mesmo interpretador. Por isso, podem ser candidatos para tarefas independentes e intensivas em código Python quando o GIL está habilitado, considerando os custos de memória e de transportar dados.

Também existem versões e configurações compatíveis com execução sem GIL. Porém, não conclua a configuração apenas pela versão do Python: verifique o interpretador efetivamente usado. Mesmo sem GIL, não há garantia automática de aceleração nem de segurança para estado compartilhado.

Justifique uma escolha com cautela

Você tem várias tarefas independentes de cálculo intenso em código Python. Justifique por que processos podem ser uma hipótese inicial no CPython com GIL habilitado. Inclua uma cautela sobre desempenho ou compartilhamento de dados.

Escreva pelo menos 100 caracteres (0/100).

Síntese para decidir

Resumo

Checklist sobre o GIL

  • Com GIL habilitado no CPython, threads do mesmo interpretador não executam código Python simultaneamente.
  • Isso limita paralelismo de processamento em Python, mas não impede progresso concorrente, especialmente durante esperas de I/O.
  • Operações nativas e bibliotecas podem ter comportamento diferente; evite regras absolutas sobre threads.
  • Processos separados não disputam um único GIL, mas têm custos de isolamento e comunicação.
  • A presença ou ausência do GIL não garante nem segurança do estado compartilhado nem ganho de desempenho.

Conclusão final

Saber a versão do Python é suficiente para concluir se threads terão paralelismo de processamento e se o estado compartilhado estará seguro.

Passo 7 de 8

Transformar os critérios em uma escolha

Use uma sequência de decisão para selecionar uma estratégia inicial — ou decidir manter a execução sequencial — considerando gargalo, bibliotecas, estado e custos.

Uma sequência para decidir

Comece pelo trabalho, não pelo nome da ferramenta

Use esta ordem: 1) identifique a etapa que mais domina o tempo; 2) verifique o tipo de API disponível; 3) considere a configuração do interpretador; 4) avalie estado compartilhado, isolamento e custo de comunicação.

O resultado é uma escolha inicial plausível, não uma promessa de que o programa ficará mais rápido.

Critérios que convergem para a escolha

A natureza da carga direciona a análise, mas bibliotecas, dados e custos podem alterar a decisão.

Diagrama de decisão: identificar gargalo, verificar API e GIL, avaliar estado e comunicação, e então considerar execução sequencial, threads, asyncio ou processos.

Não pule dos termos “I/O” ou “CPU” diretamente para uma ferramenta: os critérios se acumulam.

Escolhas iniciais para cargas predominantes

Exemplo

Espera por rede com API bloqueante

Um programa consulta muitos serviços HTTP por uma biblioteca síncrona, e cada consulta passa a maior parte do tempo esperando a rede. Se a biblioteca for adequada ao uso concorrente, threads são uma candidata inicial: uma thread que aguarda pode abrir espaço para outra avançar.

Isso não significa que qualquer chamada de rede deva usar threads; considere também os limites do serviço e o estado compartilhado.

Exemplo

Espera por rede com API assíncrona

Um programa precisa coordenar muitas operações de rede e a biblioteca oferece uma API assíncrona compatível. Asyncio é uma candidata inicial: as tarefas podem cooperar durante esperas compatíveis no laço de eventos.

Não basta a tarefa ser de I/O: uma chamada síncrona demorada no laço bloqueia o avanço das demais.

Exemplo

Cálculo Python independente

Cada entrada exige um cálculo longo executado principalmente em código Python, as entradas são independentes e o CPython está com o GIL habilitado. Processos são uma candidata inicial para obter paralelismo de processamento, desde que o custo de iniciar processos e transportar entradas e resultados seja justificável.

Se os dados forem enormes ou as tarefas forem muito curtas, esse transporte pode anular o benefício.

Quando os detalhes mudam a decisão

Estado, isolamento e etapas mistas

Threads e tarefas do mesmo laço podem acessar os mesmos objetos: isso facilita trocar dados, mas exige coordenação ao alterar estado compartilhado. Processos isolam memória, porém resultados e entradas precisam circular explicitamente.

Em uma carga mista, examine as etapas. Um pipeline pode esperar arquivos na maior parte do tempo e depois realizar uma transformação curta; outro pode ter pouca espera e um cálculo dominante. Escolha pelo gargalo relevante, não por um rótulo único para todo o programa.

Dica

Sequencial também é uma escolha válida

Mantenha a execução sequencial quando as tarefas dependem fortemente umas das outras, quando há pouco tempo de espera ou cálculo para sobrepor, ou quando coordenar dados custa mais do que o possível ganho. Trate concorrência como uma hipótese a validar no cenário real.

Prática: associe cenário e escolha inicial

Associe cada cenário à melhor escolha inicial

Relacione os cenários à estratégia inicial mais justificável.

Toque em um item e depois no par correspondente.

Passo 8 de 8

Aplicação final: justificar e revisar a decisão

Aplique os critérios de escolha a cenários concretos, explique os custos envolvidos e identifique quando uma decisão deve ser revista.

Uma escolha é uma hipótese fundamentada

Feche a decisão com quatro perguntas

Para defender uma escolha, descreva: 1) qual trabalho domina a duração; 2) qual suporte a biblioteca oferece; 3) como os dados precisam circular ou ficar isolados; e 4) qual custo pode anular o benefício.

O resultado não é uma promessa de aceleração. É uma estratégia plausível que ainda precisa ser comprovada no contexto real.

Critérios conectados à escolha

Use este mapa para organizar uma justificativa curta e completa.

Diagrama com quatro critérios — gargalo, biblioteca, estado e interpretador — apontando para três alternativas: threads, processos e asyncio; cada alternativa destaca respectivamente memória compartilhada, isolamento e laço cooperativo.

A escolha depende da combinação dos critérios, não de uma regra única.

Escolha para cenários contrastantes

Cálculo independente

Um programa precisa analisar muitos blocos independentes de dados. Cada bloco passa quase todo o tempo em cálculos executados por código Python. O CPython está com o GIL habilitado, e os resultados de cada bloco são pequenos. Qual é a escolha inicial mais plausível?

Espera de rede com API compatível

Um serviço faz muitas requisições de rede independentes. A biblioteca disponível oferece uma API assíncrona compatível, e cada requisição faz pouco processamento local. Qual é a escolha inicial mais plausível?

Justifique e revise

Decisão com condição de revisão

Cenário: várias tarefas fazem chamadas por uma biblioteca síncrona de rede; passam a maior parte do tempo esperando respostas e atualizam um pequeno conjunto de objetos em comum. O CPython usa GIL habilitado.

Qual modelo você escolheria inicialmente? Justifique relacionando o gargalo, a biblioteca e o estado compartilhado. Cite também um custo relevante e uma mudança no cenário que faria você reconsiderar a escolha.

Escreva pelo menos 180 caracteres (0/180).

Síntese da escolha responsável

Resumo

Checklist final

Antes de adotar concorrência, formule uma hipótese verificável.

  • Identifique se o gargalo relevante é espera por I/O, processamento ou uma combinação de etapas.
  • Para I/O, avalie threads quando a API bloqueante for adequada ao uso concorrente e asyncio quando houver API assíncrona compatível.
  • Para processamento intenso em código Python com GIL habilitado, processos são uma candidata quando o isolamento e a transferência de dados compensam.
  • Considere memória compartilhada, isolamento, volume de comunicação e custos de coordenação.
  • O GIL não torna o estado da aplicação automaticamente seguro; uma configuração sem GIL também não elimina a necessidade de coordenação.
  • Uma escolha plausível não comprova desempenho: ela deve ser validada no cenário real.

Decisão fundamentada

Parabéns! Você concluiu: Escolher entre threads, processos e asyncio

Muito bem! Agora você consegue justificar uma escolha entre threads, processos e asyncio, explicitar suas contrapartidas e reconhecer quando uma mudança no cenário pede uma nova análise.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso