Módulo 14 - Imutabilidade e efeitos: código previsível

Previsibilidade e concorrência: rodar em paralelo

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

O que você vai aprender

  • Entender o que é concorrência e por que ela introduz bugs difíceis.
  • Identificar a condição de corrida como disputa por dado compartilhado mutável.
  • Ligar pureza e imutabilidade à segurança em paralelo.
  • Fechar o módulo: previsibilidade é a base do código que dura.

Várias tarefas ao mesmo tempo

Computadores modernos têm vários núcleos, e usar todos ao mesmo tempo faz o trabalho render. Isso é concorrência: em vez de uma fila única de passos, várias tarefas avançam em paralelo. O ganho é real, mas vem com um perigo específico. Quando duas tarefas mexem no mesmo dado mutável ao mesmo tempo, o resultado passa a depender de quem chegou primeiro, e essa ordem é imprevisível. É como duas pessoas editando a mesma frase de um caderno na mesma hora: o que sobra na página vira sorte. Esse é o bug conhecido como condição de corrida, e ele é dos mais odiados porque muda a cada execução e some quando você tenta observá-lo.

O exemplo canônico é um simples contador compartilhado. Duas tarefas leem o valor, somam um e escrevem de volta. Se ambas leem o mesmo número antes de qualquer uma escrever, as duas gravam o mesmo resultado, e uma das somas se perde. O contador que deveria ir de dez para doze para em onze. Ninguém escreveu código errado; o erro nasce da ordem em que os acessos ao dado compartilhado se cruzaram. Reproduzir isso é penoso, porque depende de um encontro exato no tempo. Quanto mais dado mutável compartilhado, mais oportunidades para essas colisões.

Duas tarefas, A e B, apontando para um mesmo contador com valor 10 no centro. Setas mostram A lendo 10 e B lendo 10 ao mesmo tempo, cada uma somando 1 e escrevendo 11 de volta, resultando em 11 quando o correto seria 12. Ao lado, um segundo desenho mostra cada tarefa com sua própria cópia imutável, sem dado compartilhado, sem colisão.
Duas tarefas somando no mesmo contador mutável perdem uma soma. Sem dado compartilhado, não há colisão.

Pureza e imutabilidade abrem a porta

Aqui as duas ideias do módulo se encontram e mostram para que serviam. A condição de corrida precisa de dois ingredientes: dado compartilhado e mutação. Tire qualquer um dos dois e o bug não tem como nascer. Dado imutável não pode ser alterado no meio da disputa, então várias tarefas podem lê-lo à vontade, ao mesmo tempo, sem risco: ninguém muda o que o outro está lendo. Função pura não mexe em nada por fora, então rodar mil funções puras em paralelo é seguro, porque nenhuma delas pisa no dado da outra. A previsibilidade que parecia só elegância vira uma vantagem concreta: código puro e imutável é fácil de paralelizar porque não há nada compartilhado para disputar.

// Risco: contador MUTÁVEL compartilhado entre tarefas concorrentes
contador <- 10
// tarefa A e tarefa B, ao mesmo tempo:
//   leem 10, somam 1, escrevem 11  -> uma soma se perde

// Seguro: cada tarefa trabalha uma CÓPIA IMUTÁVEL e devolve seu resultado
parcialA <- processar(loteA)   // não toca em dado de B
parcialB <- processar(loteB)   // não toca em dado de A
total <- parcialA + parcialB   // combina no fim, sem disputa

Sem dado mutável compartilhado, as tarefas rodam em paralelo e só juntam os resultados no fim.

🎮 Jogo da aula

Concorrência sem susto

Julgue cada afirmação sobre concorrência, pureza e imutabilidade.

O fio que liga o módulo

Vale amarrar as pontas. Começamos escolhendo entre mudar um dado no lugar ou gerar uma cópia, e vimos que dado imutável evita mudanças por acidente à distância. Depois abrimos a função pura, que só depende dos argumentos e não toca o mundo, o que a torna previsível e trivial de testar. Em seguida, aceitamos que efeitos são necessários e aprendemos a isolá-los na borda, guardando um miolo puro. Agora fechamos mostrando o prêmio maior: essa previsibilidade toda é o que permite rodar em paralelo com segurança. O fio comum é um só, previsibilidade. Código previsível é mais fácil de entender, testar, mudar e paralelizar. Não é enfeite acadêmico; é o que faz um programa durar sem virar um campo minado a cada alteração.

Teste rápido

Por que código puro e dados imutáveis facilitam rodar tarefas em paralelo com segurança?

Perguntas frequentes

Qual a diferença entre concorrência e paralelismo?
Concorrência é estruturar o programa em tarefas que avançam de forma independente, mesmo que se intercalem num só núcleo. Paralelismo é executar de fato ao mesmo tempo, usando vários núcleos. Na prática o risco da disputa por dado é parecido nos dois. Para esta aula, o que importa é que várias tarefas mexendo no mesmo dado mutável abre a porta para o bug.
Como uma condição de corrida some quando tento depurá-la?
Porque ela depende de um encontro exato no tempo entre as tarefas. Ao rodar o depurador, você muda a velocidade e a ordem dos acessos, e o encontro que causava o bug deixa de acontecer. Por isso esses bugs têm fama de fantasmas: aparecem em produção sob carga e desaparecem quando você olha de perto. Reduzir o dado compartilhado é o remédio mais confiável.
Imutabilidade elimina a necessidade de travas e sincronização?
Reduz bastante, mas não sempre elimina. Se as tarefas só leem dado imutável e trabalham cópias próprias, você dispensa travas para esse dado. Quando, no fim, os resultados precisam ser combinados ou algo mutável é de fato compartilhado, ainda pode caber sincronização. A imutabilidade encolhe a área onde travas são necessárias, o que já simplifica muito o problema.
Se eu não escrevo código concorrente, isso me importa?
Importa, porque muita concorrência é escondida. Servidores atendem vários usuários ao mesmo tempo, interfaces respondem a eventos enquanto processam, bibliotecas paralelizam por baixo. Escrever lógica pura e evitar dado mutável compartilhado te protege dessas armadilhas mesmo sem você criar tarefas de propósito. É uma rede de segurança para um risco que aparece sozinho conforme o programa cresce.
Pureza sozinha basta para o paralelo ser seguro?
Pureza e imutabilidade caminham juntas aqui. Uma função pura não altera estado externo, e dado imutável não pode ser alterado por ninguém. Combinadas, elas garantem que nenhuma tarefa muda o que a outra usa. Se você tem funções puras mas ainda deixa um dado mutável compartilhado no meio, o risco volta. As duas ideias se reforçam para dar a segurança completa.
Esse foi o último tema teórico do curso?
Este módulo fecha o bloco sobre organizar código que dura: abstração, invariantes, imutabilidade e efeitos. O que vem a seguir é o projeto final, onde toda a trilha se junta num sistema de verdade. A previsibilidade que você viu aqui volta na prática, porque um projeto bem organizado se apoia justamente em partes puras, dados claros e efeitos isolados.

Fontes

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