Sobes.tech
Middle+

What should be done if two workers access the same data simultaneously?

sobes.tech AI

Answer from AI

It is necessary to ensure thread-safe access to data. Main approaches:

  1. Locks: Used to restrict access to critical sections of code. Only one thread can hold the lock at a time.

    # Example with Mutex in Ruby
    require 'thread'
    
    $shared_data = 0
    $mutex = Mutex.new
    
    def increment_data
      $mutex.lock # Acquire lock
      begin
        # Critical section
        temp = $shared_data
        sleep(0.01) # Simulate work
        $shared_data = temp + 1
      ensure
        $mutex.unlock # Release lock
      end
    end
    
    threads = []
    10.times do
      threads << Thread.new { increment_data }
    end
    
    threads.each(&:join)
    puts $shared_data # Will guaranteed be 10
    
  2. Database transactions: If data is stored in a database, use transactions for atomic operations. The transaction isolation level determines how changes made by other transactions are visible.

    # Example in Rails using ActiveRecord
    ActiveRecord::Base.transaction do
      # All operations inside the block are atomic
      user = User.find(user_id)
      user.balance -= amount
      order = Order.create!(user: user, amount: amount)
      user.save!
      # If something goes wrong, the entire transaction is rolled back
    end
    
  3. Atomic operations: Some languages and libraries provide atomic data types or operations that are guaranteed to execute entirely without interruption by other threads.

    # Ruby does not have built-in atomic types for arbitrary data,
    # but some operations (like assignment) can be atomic
    # for simple types. More complex scenarios require locks.
    
  4. Revision/versioning of data (Optimistic Concurrency Control): Instead of locking data, each record has a revision number or timestamp. When updating, check if the record has changed since it was read. If it has, restart the operation.

    # Example with ActiveRecord::Locking::Optimistic
    # Add `lock_version` (integer) to your table
    class Product < ApplicationRecord
      # ...
    end
    
    product = Product.find(id)
    # Another thread updates the same product...
    begin
      product.price += 10
      product.save! # Will generate ActiveRecord::StaleObjectError if lock_version changed
    rescue ActiveRecord::StaleObjectError
      # Handle conflict, e.g., reload and retry
      retry
    end
    
  5. Message brokers / Queues: Instead of direct data access, workers send messages to a broker (e.g., RabbitMQ, Sidekiq with Redis). Message processing occurs sequentially or in a controlled order, reducing conflict likelihood.

  6. Use of immutable objects: If data is immutable, concurrent access is safe because no worker can modify it.

The choice of approach or combination depends on data type, performance requirements, and application architecture. In Ruby on Rails web applications, ActiveRecord transactions and optimistic locking are often used for database data, while Mutex is used for in-memory resources. Background tasks (like Sidekiq) typically process messages from queues, which inherently provide some level of ordering.