如何在 rails 上使 ruby 中的线程安全?

How to make a thread safe in ruby on rails?

我有一个计算,但我的计算不是线程安全的,它必须使用增量或increment_counter,但我不知道如何实现它。这是我的代码

def create
if @purchase.check_quantity(@product, purchase_params[:quantity].to_i)
  return render :new  
end

#calculation is not thread-safe
new_quantity = @product.quantity - purchase_params[:quantity].to_i

@purchase.assign_attributes(purchase_params)
if @purchase.save
   @product.update(:quantity => new_quantity)
   redirect_to product_url(@product)
else
  flash[:error] = @purchase.errors.full_messages.join(', ')
  render :new
end

结束

我不确定你的代码是什么“不是 thread-safe”,但我不认为你的问题是线程安全。

如果您认为 @purchase@product 对象在该代码被命中之前发生变化,我建议您使用 .reload. If you believe your calculation needs to run asynchronously you need to consider using an async job framework like ActiveJob or Sidekiq.

您已经注意到您可能 运行 进入竞争条件,在这种情况下,对您的应用程序的两个请求几乎同时进入该方法。两者都读取 @product.quantity,都计算 new_quantity,然后您最终得到其中一个请求将新值写入数据库,而另一个稍慢的请求覆盖了该值。

有两种方法可以解决这个问题。

  1. 锁定记录以进行更新。尝试同时更新同一记录的另一个进程将引发错误。 Ruby Rails 支持 Locking::Optimistic and Locking::Pessimistic。这在一定程度上取决于您的具体情况 use-case 哪种方法可能更好。

  2. 另一种方法可能是在 SQL 中进行更新,以原子方式更新记录。 Rails 有方法 update_counters 可以让你像这样计算和更新数据库中的记录:

    Product.update_counters(@product.id, quantity: -purchase_params[:quantity].to_i)
    

    但这种方式可能(取决于你的 check_quantity 内部所做的)可能仍然会导致竞争条件问题,因为该方法中的检查将不知道任何其他请求在紧接之前通过检查并更新数据库紧随其后。

这两种方法都可能有助于解决问题。但选择哪种方法取决于您的具体用例以及您需要的安全级别、用户体验和数据完整性。