2

Foobar.find(1).votes_count返回 0。

在 Rails 控制台中,我正在做:

10.times { Resque.enqueue(AddCountToFoobar, 1) }

我的resque工人:

class AddCountToFoobar
  @queue = :low

  def self.perform(id)
    foobar = Foobar.find(id)
    foobar.update_attributes(:count => foobar.votes_count +1)
  end
end

我希望Foobar.find(1).votes_count10,但它返回 4。如果我10.times { Resque.enqueue(AddCountToFoobar, 1) }再次运行,它会返回相同的行为。它只增加votes_count4,有时增加 5。

谁能解释一下?

4

3 回答 3

3

这是一个经典的竞争条件场景。想象一下,只有 2 个工人存在,并且他们每个人都在运行你的一项增加投票的工作。想象以下序列。

  1. Worker1:加载 foobar(投票数 == 1)
  2. Worker2:加载 foobar(投票数 == 1,在单独的 ruby​​ 对象中)
  3. 工人 1:增加投票计数(现在 == 2)并保存
  4. 工人 2:增加 foobar 的副本(现在投票数 == 2)并保存,覆盖工人 1 所做的

尽管 2 个工作人员每人运行 1 个更新作业,但计数仅增加了 1,因为他们都在自己的 foobar 副本上进行操作,而没有意识到其他工作人员正在做的更改

要解决这个问题,您可以进行就地样式更新,即

UPDATE foos SET count = count + 1

或使用两种锁定活动记录支持形式之一(悲观锁定和乐观锁定)

前者有效,因为数据库确保您不会同时对同一行进行并发更新。

于 2012-04-21T03:18:01.773 回答
0

看起来 ActiveRecord 在 Resque(或者更确切地说是 redis,我猜)中不是线程安全的。这是一个很好的解释

于 2012-04-20T22:44:28.477 回答
-1

正如弗雷德里克所说,您正在观察竞争条件。从读取值并更新它开始,您需要序列化对关键部分的访问。

我会尝试使用悲观锁定: http : //api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

foobar = Foobar.find(id)
foobar.with_lock do
  foobar.update_attributes(:count => foobar.votes_count +1)
end
于 2012-04-21T04:26:56.080 回答