在Django中锁定对象的最简单方法是什么

时间:2009-03-30 20:49:21

标签: django concurrency

当用户在update_object视图中处于活动状态时尝试删除对象时,我想引发错误。我觉得需要某种类似互斥锁的机制。你有什么建议吗?

4 个答案:

答案 0 :(得分:14)

所以,有一些方法可以做你所要求的。但是其中很多都不是独立于实现的:你可以使用锁或rlocks,但它们实际上只能在100%线程服务器上工作,而且在fork / pre-fork实现中可能根本不工作。

这或多或少意味着锁定实现将取决于您。两个想法:

    文件系统上的
  1. .lock文件
  2. 模型类中的
  3. locked属性
  4. 在这两种情况下,您都必须在更新时手动设置锁定对象,并在删除时检查它。尝试类似:

    def safe_update(request,model,id):
        obj = model.objects.get(id)
        if obj.locked:
            raise SimultaneousUpdateError #Define this somewhere
        else:
            obj.lock()
            return update_object(request,model,id)
    
    # In models file
    class SomeModel(models.Model):
        locked = models.BooleanField(default = False)
        def lock(self):
            self.locked = True
            super(models.Model,self).save()
        def save(self):
            # overriding save because you want to use generic views
            # probably not the best idea to rework model code to accomodate view shortcuts
            # but I like to give examples.
            self.locked = False
            # THIS CREATES A DIFFERENT CRITICAL REGION!
            super(models.Model,self).save()
    

    这确实是一个笨拙的实施,你必须清理。您可能对创建不同的关键区域这一事实感到不舒服,但如果您将数据库用作实现而不使实现变得更复杂,我不会看到您将如何做得更好。 (一种选择是使锁完全独立的对象。然后你可以在调用save()方法后更新它们。但是我不想编码那么。)如果你真的想使用基于文件的锁定系统,这也将解决问题。如果你是数据库命中偏执狂,这可能适合你。类似的东西:

    class FileLock(object):
        def __get__(self,obj):
            return os.access(obj.__class__+"_"+obj.id+".lock",os.F_OK)
        def __set__(self,obj,value):
            if not isinstance(value,bool):
                raise AttributeError
            if value:
                f = open(obj.__class__+"_"+obj.id+".lock")
                f.close()
            else:
                os.remove(obj.__class__+"_"+obj.id+".lock")
        def __delete__(self,obj):
            raise AttributeError
    
     class SomeModel(models.Model):
         locked = FileLock()
         def save(self):
             super(models.Model,self).save()
             self.locked = False
    

    无论如何,也许有一些方法可以根据您的喜好混合和匹配这些建议吗?

答案 1 :(得分:6)

自添加select_for_update以来,只要数据库支持,就可以通过一种简单的方法来获取对象的锁定。根据Django文档,postgresql,oracle和mysql至少支持它。

示例代码:

import time

from django.contrib.auth import get_user_model
from django.db import transaction


User = get_user_model()

target_user_pk = User.objects.all()[0].pk


with transaction.atomic():
    print "Acquiring lock..."
    to_lock = User.objects.filter(pk=target_user_pk).select_for_update()
    # Important! Queryset evaluation required to actually acquire the lock.
    locked = to_lock[0]
    print locked

    while True:
        print "sleeping {}".format(time.time())
        time.sleep(5)

答案 2 :(得分:1)

由于您的范围仅限于删除,而不是更新,因此一个选项是重新考虑将“删除”视为“取消发布”操作。例如,采用以下模型:

class MyManager(models.Manager):
    def get_query_set(self):
        super(MyManager, self).get_query_set().filter(published=True)

class MyModel(models.Model):
    objects = MyManager()
    published = models.BooleanField(default=True)
    ... your fields ...

    def my_delete(self):
        self.published = False
        super(MyModel, self).save()

    def save(self):
        self.published = True
        super(MyModel, self).save()

通过这种方式,无论何时提交编辑,所有用户都可以看到...但其他用户仍然可以自由删除项目。此技术的一个优点是您不必拥有任何额外的逻辑来锁定项目并向用户显示不同的UI。缺点是db表中使用了额外的空间,以及罕见的情况下删除的项目“神奇地”再次出现。

(这可能只是一个起点。如果你采用这条路径,你可能希望根据你的使用情况对这个想法做一些修改。)

答案 3 :(得分:0)

我建议使用简单的读写锁,因为您不希望阻止用户同时访问对象(仅限编辑)。

执行此操作的一般方法是创建一个维护活动读者数量的函数。当您需要写入该对象时,您将创建另一个函数来阻止新读者访问(想想维护页面),并可能将现有读者重定向。一旦没有剩下的读者,你就可以完成你的写作,然后解锁对象。