解决轨道中的竞争条件(Y中有限的X数)

时间:2012-03-02 15:24:16

标签: mysql ruby-on-rails locking race-condition

编辑:使用MySQL ...

假设您有一个应用程序,可以将学生添加到课程中,并且该课程的空间有限......所以您可以这样做:

def add
  if some_classroom.size < MAX_SIZE
    add_student_to_class
  end
end

这是多线程环境中的竞争条件。跛。

假设我们

  1. 不要这样,
  2. 不想锁定我们的教室桌面或记录(导致我们的应用程序在其他地方吮吸)
  3. 我们做什么?

    我建议:

    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方法实际上是单线程的,因为任何额外的进程都将被强制等待锁定!直到锁被释放为止。

    这有用吗?也许?我认同?我不知道......

    我已经用多个流程一次性地完成了这个工作,但是很难确定(毕竟这是竞争条件)。

    任何人都可以提供一些额外的见解吗?

1 个答案:

答案 0 :(得分:0)

您好,

我想象一个非常简单的SQL方法,只需要注意一点:添加一个名为list_position的附加列,并为包含classroom_id的课堂 - 学生 - 关系表添加一个唯一索引, student_idlist_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