Pular para o conteúdo

Boas práticas e padrões · Concorrência · 5 min de leitura

Condições de corrida e imutabilidade

Uma condição de corrida acontece quando duas tarefas leem e escrevem o mesmo dado ao mesmo tempo e o resultado depende de quem chega primeiro. Imutabilidade e confinamento evitam o problema na raiz.

saldo = 100 → Tarefa A1saldo = 100 → Tarefa B1Tarefa A → saldo = 1302Tarefa B → saldo = 1303saldo = 100saldo = 100Tarefa A — lê 100, soma 50Tarefa Alê 100, soma 50Tarefa B — lê 100, soma 30Tarefa Blê 100, soma 30saldo = 130 — deveria ser 180saldo = 130deveria ser 180

Arraste para o lado para ver o diagrama inteiro.

"Ler, calcular, escrever" não é uma operação só: no meio, outra tarefa pode ter lido o valor antigo.

O problema

contador++ parece uma operação, mas são três: ler, somar, escrever. Com duas threads (ou duas corrotinas em threads diferentes), uma escrita pode apagar a outra — o bug aparece de vez em quando, some no debugger e volta em produção. O mesmo vale para dois usuários editando o mesmo registro no servidor.

Soluções, da melhor para a pior

  1. Imutabilidade: objetos que não mudam podem ser lidos por qualquer um. Crie um novo valor em vez de alterar (copy(), val, struct, readonly).
  2. Confinamento: só uma thread ou um actor mexe no estado (Main, actor, Dispatchers.Main).
  3. Operações atômicas: MutableStateFlow.update { }, AtomicInteger, UPDATE … SET saldo = saldo + 50 no banco.
  4. Locks (Mutex, synchronized): funcionam, mas é fácil esquecer ou travar (deadlock).

No banco de dados

Duas requisições lendo e gravando o mesmo registro também correm. Use atualização atômica no SQL, transação com o nível de isolamento adequado ou controle otimista (coluna de versão: só grava se a versão não mudou — o HTTP 412 existe para isso).

No código: antes e depois

✗ AntesKotlin
class Carrinho {
    var itens = mutableListOf<Item>()
    var total = 0.0
    fun adicionar(i: Item) { // chamado de várias corrotinas em Dispatchers.Default
        itens.add(i)          // ArrayList não é thread-safe
        total += i.preco      // ler + somar + escrever: corrida
    }
}
✓ DepoisKotlin
data class Carrinho(val itens: List<Item> = emptyList()) {
    val total get() = itens.sumOf { it.preco } // derivado, nunca fora de sincronia
}

class CarrinhoStore {
    private val _estado = MutableStateFlow(Carrinho())
    val estado = _estado.asStateFlow()

    // update {} tenta de novo se outro escreveu no meio (compare-and-set)
    fun adicionar(i: Item) = _estado.update { it.copy(itens = it.itens + i) }
}

Estado imutável + update atômico: não há como duas adições se perderem, e o total é sempre calculado a partir dos itens.

Em resumo

Perguntar à IA

Monte um prompt pronto sobre Condições de corrida e imutabilidade na sua linguagem e no seu caso, e abra num chat gratuito.

ChatGPT Claude Perplexity

O prompt vai para o site que você escolher. Não cole senhas, tokens nem dados reais de clientes. Para Gemini e outros, copie e cole.

Referências

Continue aprendendo

Todos os temas de boas práticas e padrões →