Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/300.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python django中计数器的原子增量_Python_Django_Transactions_Race Condition - Fatal编程技术网

Python django中计数器的原子增量

Python django中计数器的原子增量,python,django,transactions,race-condition,Python,Django,Transactions,Race Condition,我试图在Django中以原子方式递增一个简单计数器。我的代码如下所示: from models import Counter from django.db import transaction @transaction.commit_on_success def increment_counter(name): counter = Counter.objects.get_or_create(name = name)[0] counter.count += 1 count

我试图在Django中以原子方式递增一个简单计数器。我的代码如下所示:

from models import Counter
from django.db import transaction

@transaction.commit_on_success
def increment_counter(name):
    counter = Counter.objects.get_or_create(name = name)[0]
    counter.count += 1
    counter.save()
如果我正确理解Django,这应该将函数包装在事务中,并使增量成为原子的。但它不起作用,计数器更新中存在竞争条件。如何使此代码成为线程安全的?

使用:

从django.db.models导入F
更新()
中:

Counter.objects.get\u或\u create(name=name)
Counter.objects.filter(name=name).update(count=F(“count”)+1)
或在对象实例上:

counter,u=counter.objects.get_或_create(name=name)
counter.count=F(“count”)+1
保存(更新_字段=[“计数”])
请记住指定
update_字段
,否则您可能会在模型的其他字段上遇到竞争条件


官方文档中已经添加了关于的注释。

或者,如果您只需要一个计数器而不是持久对象,则可以使用用C实现的itertools计数器。GIL将提供所需的安全性


--Sai

在Django 1.4中有一些子句,使用数据库锁来确保没有数据被错误地并发访问

保持简单,以@Oduvan的答案为基础:

counter, created = Counter.objects.get_or_create(name = name, 
                                                 defaults={'count':1})
if not created:
    counter.count = F('count') +1
    counter.save()
这里的优点是,如果对象是在第一条语句中创建的,则无需进行任何进一步的更新。

Django 1.7

from django.db.models import F

counter, created = Counter.objects.get_or_create(name = name)
counter.count = F('count') +1
counter.save()

如果设置计数器时不需要知道计数器的值,那么最好的答案肯定是您的最佳选择:

counter, _ = Counter.objects.get_or_create(name = name)
counter.count = F('count') + 1
counter.save()
这会告诉您的数据库在
count
的值上加1,它可以在不阻塞其他操作的情况下很好地完成此操作。缺点是您无法知道刚刚设置的
count
。如果两个线程同时命中这个函数,它们都会看到相同的值,并且都会告诉db添加1。db最终会按预期添加2个,但您不知道哪一个先添加

如果您现在确实关心计数,您可以使用Emil Stenstrom引用的
select\u for\u update
选项。下面是它的样子:

from models import Counter
from django.db import transaction

@transaction.atomic
def increment_counter(name):
    counter = (Counter.objects
               .select_for_update()
               .get_or_create(name=name)[0]
    counter.count += 1
    counter.save()

这将读取当前值并锁定匹配行,直到事务结束。现在一次只能有一个工人阅读。有关选择更新的详细信息,请参阅。

是否应将其包装在提交成功方法中?一个问题是,如果以后需要更新的值,则需要从数据库中获取它。在某些情况下,如ID生成,这可能会导致竞争条件。例如,两个线程可能会以原子方式递增一个ID(比如从1到3),但随后两个线程都会查询当前值并获取3,尝试插入,爆炸。。。在第二个版本中,为什么不使用默认的kwarg来获取_或_create,然后将F对象放入一个
块中(如果创建了
块)?在创建的情况下应该更快,对吗?我继续写了一个答案来演示我的意思。这绝对是正确的答案。检查:使用F()的另一个好处是,让数据库(而不是Python)更新字段的值可以避免竞争条件。
get\u或\u create
返回一对,因此它应该是
计数器,created=…
就像在姆利斯纳的回答中一样。询问者特别询问如何在数据库中以原子方式递增字段。在这家伙的辩护中,没有明确说明这一点。显然是有意的。这是我最终采用的解决方案,并将块包装到事务中。提交成功。真正的问题是不同客户机对相同数据的竞争。内部锁可以确保每个客户端为其事务获取自己的序列号。在我看来,不使用
+=
来避免竞争条件似乎是一种浪费。Python用户应该已经知道
a+=b
a=a+b
之间有区别,所以为什么不使用它呢?也许它会与一些缓存数据冲突?不确定。这个答案有最好的解释。直到读到这篇文章,我才确信
count=F('count')+1
会起作用