Sobes.tech
Middle+

O que deve ser feito se dois trabalhadores acessarem os mesmos dados ao mesmo tempo?

sobes.tech IA

Resposta da IA

É necessário garantir a segurança no acesso aos dados. Abordagens principais:

  1. Bloqueios (Locks): São usados para limitar o acesso às secções críticas do código. Apenas um thread pode possuir o bloqueio num dado momento.

    # Exemplo com Mutex em Ruby
    require 'thread'
    
    $shared_data = 0
    $mutex = Mutex.new
    
    def increment_data
      $mutex.lock # Aquisição do bloqueio
      begin
        # Secção crítica
        temp = $shared_data
        sleep(0.01) # Simulação de trabalho
        $shared_data = temp + 1
      ensure
        $mutex.unlock # Liberação do bloqueio
      end
    end
    
    threads = []
    10.times do
      threads << Thread.new { increment_data }
    end
    
    threads.each(&:join)
    puts $shared_data # Garantido será 10
    
  2. Transações de bases de dados: Se os dados estão armazenados numa base de dados, use transações para operações atómicas. O nível de isolamento das transações determina como as alterações feitas por outras transações são visíveis.

    # Exemplo em Rails usando ActiveRecord
    ActiveRecord::Base.transaction do
      # Todas as operações dentro do bloco são executadas de forma atómica
      user = User.find(user_id)
      user.balance -= amount
      order = Order.create!(user: user, amount: amount)
      user.save!
      # Se algo correr mal, toda a transação é revertida
    end
    
  3. Operações atómicas: Algumas linguagens e bibliotecas fornecem tipos de dados ou operações atómicas, que são garantidamente executadas na sua totalidade, sem possibilidade de interrupção por outros threads.

    # Ruby não possui tipos atómicos incorporados para dados arbitrários,
    # mas algumas operações (por exemplo, atribuição) podem ser atómicas
    # para tipos simples. Para cenários mais complexos, são necessários bloqueios.
    
  4. Revisões / Controlo de versões de dados (Optimistic Concurrency Control): Em vez de bloquear dados, cada registo tem um número de revisão ou marca temporal. Ao atualizar, verifica-se se o registo não mudou desde a sua leitura. Se mudou, a operação reinicia.

    # Exemplo com ActiveRecord::Locking::Optimistic
    # Adicione `lock_version` (inteiro) à sua tabela
    class Product < ApplicationRecord
      # ...
    end
    
    product = Product.find(id)
    # Outro thread atualiza o mesmo produto...
    begin
      product.price += 10
      product.save! # Gera ActiveRecord::StaleObjectError se lock_version mudou
    rescue ActiveRecord::StaleObjectError
      # Tratamento do conflito, por exemplo, recarregar e tentar novamente
      retry
    end
    
  5. Intermediários / Filas de mensagens: Em vez de acesso direto aos dados, os trabalhadores enviam mensagens a um intermediário (por exemplo, RabbitMQ, Sidekiq com Redis). O processamento das mensagens é feito sequencialmente ou numa ordem controlada, o que reduz a probabilidade de conflitos.

  6. Utilização de objetos imutáveis: Se os dados forem imutáveis, o acesso simultâneo a eles é seguro, pois nenhum trabalhador pode alterá-los.

A escolha da abordagem ou combinação de abordagens depende do tipo de dados, dos requisitos de desempenho e da arquitetura da aplicação. Em aplicações web com Ruby on Rails, costuma-se usar transações ActiveRecord e bloqueios otimistas para dados na base de dados, e mutex para recursos em memória. As tarefas em segundo plano (como Sidekiq) geralmente gerenciam mensagens da fila, que por si só garantem uma certa ordenação.