Я пытаюсь атомарно увеличить простой счетчик в 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, это должно заключить функцию в транзакцию и сделать приращение атомарным. Но это не работает, и в обновлении счетчика есть состояние гонки. Как сделать этот код поточно-ориентированным?
На мой взгляд, не использовать += во избежание состояния гонки - такая трата времени. Пользователи Python уже должны знать, что между a += b и a = a + b есть разница, так почему бы не использовать это? Может это будет противоречить каким-то данным кеша? Точно сказать не могу. aliqandil 19.04.2017
Это говорит вашей базе данных добавить 1 к значению count, что она может отлично справиться, не блокируя другие операции. Недостатком является то, что у вас нет возможности узнать, что count вы только что установили. Если два потока одновременно задействуют эту функцию, они оба увидят одно и то же значение и оба скажут базе данных добавить 1. База данных в конечном итоге добавит 2, как и ожидалось, но вы не узнаете, какой из них пошел первым.
Если вас действительно интересует счетчик прямо сейчас, вы можете использовать параметр select_for_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()
Это считывает текущее значение и блокирует совпадающие строки до конца транзакции. Теперь одновременно читать может только один рабочий. См. документы для получения дополнительной информации о select_for_update.
Ответ 4 · рейтинг 8 · mlissner
Сохраняя простоту и опираясь на ответ @ Oduvan:
counter, created = Counter.objects.get_or_create(name = name,
defaults={'count':1})
if not created:
counter.count = F('count') +1
counter.save()
Преимущество здесь в том, что если объект был создан в первом операторе, вам не нужно делать никаких дальнейших обновлений.
Ответ 5 · рейтинг 5 · derevo
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()
Ответ 6 · рейтинг -2 · Sai Venkat
Или, если вам просто нужен счетчик, а не постоянный объект, вы можете использовать счетчик itertools, который реализован в C. GIL обеспечит необходимую безопасность.