编辑:使用MySQL ...
假设您有一个应用程序,可以将学生添加到课程中,并且该课程的空间有限......所以您可以这样做:
def add
if some_classroom.size < MAX_SIZE
add_student_to_class
end
end
这是多线程环境中的竞争条件。跛。
假设我们
我们做什么?
我建议:
class Classroom < ActiveRecord::Base
has_one :classroom_lock
after_create :create_lock_record
def create_lock_record
c = ClassroomLock.new
c.classroom = self
c.save!
end
end
class ClassroomLock < ActiveRecord::Base
belongs_to :classroom
end
def add
c = Classroom.first
ActiveRecord::Base.transaction do
c.classroom_lock.lock!
c = Classroom.first #load this again (it might have changed)
if c.size < MAX_SIZE
c.add_new_student(some_student)
else
do_stuff_about_not_enough_room
end
end
end
这看起来应该很棒。我的(虚构)课堂#show方法没有阻止,因为课堂记录实际上没有被锁定,并且add方法实际上是单线程的,因为任何额外的进程都将被强制等待锁定!直到锁被释放为止。
这有用吗?也许?我认同?我不知道......
我已经用多个流程一次性地完成了这个工作,但是很难确定(毕竟这是竞争条件)。
任何人都可以提供一些额外的见解吗?
答案 0 :(得分:0)
您好,
我想象一个非常简单的SQL方法,只需要注意一点:添加一个名为list_position
的附加列,并为包含classroom_id
的课堂 - 学生 - 关系表添加一个唯一索引, student_id
和list_position
。
通过将list_position
设置为课堂上的学生人数加1来插入。 Ruby代码可能会在某处
classroomStudent = ClassroomStudent.new
classroomStudent.list_position = classroom.size + 1
until classroomStudent.save
classroomStudent.list_position += 1
do_stuff_about_not_enough_room if classroomStudent.list_position > MAX_SIZE
end
结果:如果两个插入尝试同时将学生插入同一个类,则唯一索引将使一个学生退出(INSERT
失败)。这意味着您可能需要重试list_position=MAX_SIZE
,但保证您在课堂上永远不会有太多学生。
您也可以使用此方法构建等待队列。
如果您的关系表中已有数据,则必须先为list_position
添加值。我猜想会像
UPDATE ClassroomStudents AS c1 SET c1.list_position = COALESCE((SELECT MAX(c2.list_position) FROM ClassroomStudents AS c2 WHERE c2.classroom_id = c1.classroom_id), 0) + 1;
会在这里完成这项工作,虽然它可能有点慢。
我知道解决方案仍有一些粗糙的边缘,但也许有帮助。
此致
TC