在model.save()中处理竞态条件 [英] Handling race condition in model.save()

查看:244
本文介绍了在model.save()中处理竞态条件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何在模型的 save()方法中处理可能的竞争条件?

How should one handle a possible race condition in a model's save() method?

以下示例将实现具有相关项目的有序列表的模型。创建新项目时,将使用当前列表大小作为其位置。

For example, the following example implements a model with an ordered list of related items. When creating a new Item the current list size is used as its position.

从我可以知道,如果同时创建多个项目,这可能会出错。

From what I can tell, this can go wrong if multiple Items are created concurrently.

class OrderedList(models.Model):
    # ....
    @property
    def item_count(self):
        return self.item_set.count()

class Item(models.Model):
    # ...
    name   = models.CharField(max_length=100)
    parent = models.ForeignKey(OrderedList)
    position = models.IntegerField()
    class Meta:
        unique_together = (('parent','position'), ('parent', 'name'))

    def save(self, *args, **kwargs):
        if not self.id:
            # use item count as next position number
            self.position = parent.item_count
        super(Item, self).save(*args, **kwargs)



我们遇到了 @transactions .commit_on_success()但似乎只适用于视图。即使它适用于模型方法,我仍然不知道如何正确处理一个失败的事务。

I've come across @transactions.commit_on_success() but that seems to apply only to views. Even if it did apply to model methods, I still wouldn't know how to properly handle a failed transaction.

我正在处理它这样,但感觉更多像一个hack比解决方案

I am currenly handling it like so, but it feels more like a hack than a solution

def save(self, *args, **kwargs):
    while not self.id:
        try:
            self.position = self.parent.item_count
            super(Item, self).save(*args, **kwargs)
        except IntegrityError:
            # chill out, then try again
            time.sleep(0.5)

建议?

上述解决方案的另一个问题是 while如果 IntegrityError 是由名称冲突(或任何其他唯一字段)。

Another problem with the above solution is that the while loop will never end if IntegrityError is caused by a name conflict (or any other unique field for that matter).

对于记录,这里是我到目前为止我似乎需要做什么:

For the record, here's what I have so far which seems to do what I need:

def save(self, *args, **kwargs):   
    # for object update, do the usual save     
    if self.id: 
        super(Step, self).save(*args, **kwargs)
        return

    # for object creation, assign a unique position
    while not self.id:
        try:
            self.position = self.parent.item_count
            super(Step, self).save(*args, **kwargs)
        except IntegrityError:
            try:
                rival = self.parent.item_set.get(position=self.position)
            except ObjectDoesNotExist: # not a conflict on "position"
                raise IntegrityError
            else:
                sleep(random.uniform(0.5, 1)) # chill out, then try again


推荐答案

这可能会感觉到对你的攻击,但对我来说,它似乎是一个合理,合理的乐观并发方法的实现 - 尝试做任何事情,检测由种族条件引起的冲突,如果出现,请稍后重试。一些数据库系统地使用它而不是锁定,并且它可以导致更好的性能,除非在很多的写入负载(在现实生活中很少见)的系统下。

It may feel like a hack to you, but to me it looks like a legitimate, reasonable implementation of the "optimistic concurrency" approach -- try doing whatever, detect conflicts caused by race conditions, if one occurs, retry a bit later. Some databases systematically uses that instead of locking, and it can lead to much better performance except under systems under a lot of write-load (which are quite rare in real life).

我喜欢它很多,因为我把它看作是Hopper原则的一般情况:很容易要求宽恕比许可,它广泛应用于编程(特别是但不限于Python) -

I like it a lot because I see it as a general case of the Hopper Principle: "it's easy to ask forgiveness than permission", which applies widely in programming (especially but not exclusively in Python -- the language Hopper is usually credited for is, after all, Cobol;-).

我推荐的一个改进是等待随机 >时间量 - 避免两个进程同时尝试的元竞态条件,两者都发现冲突,并且同时再次重试,导致饥饿。 time.sleep(random.uniform(0.1,0.6))等应该就足够了。

One improvement I'd recommend is to wait a random amount of time -- avoid a "meta-race condition" where two processes try at the same time, both find conflicts, and both retry again at the same time, leading to "starvation". time.sleep(random.uniform(0.1, 0.6)) or the like should suffice.

改进是延长预期的等待,如果更多的冲突被满足 - 这是所谓的指数退避在TCP / IP(你不必指数增长,即每次一个常数乘数> 1,当然,但是这种方法有很好的数学属性)。我们只保证限制非常写入系统的问题(在尝试写入期间发生多次冲突的情况很常见),并且在您的特定情况下可能不值得。

A more refined improvement is to lengthen the expected wait if more conflicts are met -- this is what is known as "exponential backoff" in TCP/IP (you wouldn't have to lengthen things exponentially, i.e. by a constant multiplier > 1 each time, of course, but that approach has nice mathematical properties). It's only warranted to limit problems for very write-loaded systems (where multiple conflicts during attempted writes happen quite often) and it may likely not be worth it in your specific case.

这篇关于在model.save()中处理竞态条件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆