O bug mais traiçoeiro que existe: um código que funciona perfeitamente sozinho — e quebra, de forma imprevisível, só quando dois processos rodam ao mesmo tempo.
O escalonador troca de processo tão rápido que parece que tudo roda ao mesmo tempo — concorrência.
Quando dois processos concorrem por um mesmo recurso compartilhado, a ordem exata das trocas de CPU passa a importar — e muito.
Cada caixa, sozinho, faz a conta certa. O problema não está na lógica de nenhum dos dois — está na simultaneidade.
Acontece sempre que dois ou mais processos acessam um dado compartilhado, e o resultado final depende de quem chega primeiro — uma corrida cujo vencedor muda a cada execução.
Um processo lê o saldo, soma R$ 100, grava de volta. Sem concorrência, sempre dá certo.
Dois processos fazem exatamente essa sequência ao mesmo tempo — e o resultado passa a depender do acaso.
Ler o saldo, somar na memória do processo, e gravar de volta. O escalonador pode trocar de processo entre qualquer uma dessas três etapas — e é exatamente aí que o bug nasce.
Resultado final: R$ 200, não R$ 300 — o depósito do Caixa A foi perdido, porque o Caixa B leu o saldo antes de A terminar de gravar.
É exatamente o trecho "ler → calcular → gravar" do slide anterior. Enquanto um processo está dentro dessa região, nenhum outro processo pode entrar nela ao mesmo tempo — senão a corrida acontece de novo.
Se o Caixa B tivesse que esperar o Caixa A terminar de gravar antes de sequer começar a ler o saldo, a corrida nunca aconteceria. Garantir isso é o papel da exclusão mútua.
Qualquer um pode entrar na região crítica a qualquer momento — mesmo com outro processo já lá dentro.
Enquanto um processo está na região crítica, todos os outros que tentarem entrar ficam bloqueados esperando a vez.
Essas quatro regras (Tanenbaum) são o critério pra avaliar qualquer mecanismo de sincronização que vocês encontrarem depois — semáforos, mutexes, locks.
A forma mais simples de esperar a vez é ficar num laço checando repetidamente "já posso entrar?" — isso é espera ocupada. Resolve a exclusão mútua, mas o processo que espera consome CPU o tempo todo, só checando.
O processo em espera fica "girando" na CPU sem fazer trabalho útil — o oposto de eficiente.
Mecanismos como mutexes e semáforos colocam o processo em espera no estado bloqueado, sem gastar CPU — leitura ⚪ de hoje.
Esse é o tipo de bug que "funciona na minha máquina", passa nos testes, e falha esporadicamente em produção — porque só aparece sob uma ordem específica de execução, difícil de reproduzir.
Qualquer recurso acessado por mais de um processo ou thread ao mesmo tempo é candidato a condição de corrida.
Bancos de dados usam transações; código concorrente usa locks/mutexes — todos aplicando a mesma ideia de hoje.
A N2 (Encontro 8) pede uma defesa com o container do projeto rodando ao vivo. Hoje é o ensaio: cada equipe pratica o ciclo completo — construir, subir, mostrar funcionando, e derrubar — sem surpresas no dia da defesa.
docker build → docker run → demonstração → docker stopO ciclo inteiro, do zero, cronometrado — igual ao que a equipe vai fazer na defesa da N2.
Container que não sobe, porta ocupada, Dockerfile desatualizado — melhor descobrir hoje do que durante a defesa.
Uma sequência real: rodar o script que expõe a condição de corrida, e depois o ciclo completo de ensaio do container.
Cada uma dessas linhas vira um passo do roteiro de laboratório de hoje.
Rodar um script simples que expõe o bug de contagem concorrente, repetidas vezes, e observar resultados diferentes.
Cada equipe cronometra o próprio ciclo de build → run → demonstração → stop.
O roteiro de laboratório detalha os passos, com dicas se algo travar.
A base teórica de hoje, direto da fonte — o Laureano não desdobra concorrência, então esta leitura é a referência principal do encontro. p. 82–84
Pra quem quer entender os mecanismos reais de sincronização além do conceito básico. p. 84–94 e 115–119
O quiz da semana verifica essa leitura e recolhe suas observações da prática — tudo no mesmo instrumento.