Foobar.find(1).votes_count
返回0.
在rails控制台中,我正在做:
10.times { Resque.enqueue(AddCountToFoobar, 1) }
我的工作人员:
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_count
为10
,但它会返回4.如果我再次运行10.times { Resque.enqueue(AddCountToFoobar, 1) }
,它会返回相同的行为。它仅将votes_count
增加4,有时增加5。
任何人都能解释一下吗?
答案 0 :(得分:3)
这是一种经典的竞赛条件。想象一下,只有2名工人存在,他们每个人都会运行一个增加投票的工作。想象一下以下序列。
虽然每个工作人员分别运行1个更新工作,但计数仅增加1,因为他们都在自己的foobar副本上运行,而这个副本并不知道其他工作人员正在做的更改
要解决此问题,您可以进行就地样式更新,即
UPDATE foos SET count = count + 1
或使用锁定活动记录支持的两种形式之一(悲观锁定和乐观锁定)
前者有效,因为数据库确保您不会同时在同一行上进行并发更新。
答案 1 :(得分:0)
看起来ActiveRecord在Resque中不是线程安全的(或者更确切地说是redis,我猜)。 Here's a nice explanation
答案 2 :(得分:-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