【问题标题】:Django - how to deal with concurrency?Django - 如何处理并发?
【发布时间】:2014-02-06 14:32:12
【问题描述】:

我目前正在开发一款游戏,用户可以在其中获得经验值。我的(自定义)用户模型如下所示:

class TriariadUser(AbstractBaseUser, PermissionsMixin):
    pseudonym = models.CharField(max_length=40)
    level = models.PositiveSmallIntegerField(default=1)
    experience_points = models.PositiveIntegerField(default=0)

    def add_ep_points(self, points):
        self.experience_points += points

        if self.experience_points >= get_next_level_ep(self.level):
            level += 1

        self.save()

现在我有各种信号监听器,可以为用户添加体验点。问题是:如果在一个请求期间获得多个 XP,则最后一次获得的 XP 会覆盖所有其他的。

显然这是一个竞争条件,所以我尝试将我的函数修改为静态并使用select_for_update

@staticmethod
def add_ep_points(user_id, points):
    user = TriariadUser.objects.select_for_update.get(pk=user_id)

    user.experience_points += points

    ...

这按预期工作,但是模板中的用户对象没有更新。也就是说,必须提出一个新的请求,让用户看到发生了什么。使用 django 调试工具栏我可以看到,加载用户的请求是在开始时发出的。之后进行所有相关更新。但是之后用户并没有重新加载,所以显示的是旧状态。

我可以想到各种解决方法,例如使用 JavaScript 重新加载,但必须有一些其他解决方案(至少我希望如此)。

有没有办法在 django 中锁定对象?有没有办法告诉一个对象需要重新加载?有没有更好的方法来实现这一点(也许使用某种中间件?)。

【问题讨论】:

    标签: python django concurrency locking django-signals


    【解决方案1】:

    为了避免这种情况,我会这样做。首先创建一个UserExpRecord 模型,其中包含与用户的关系以及您要添加或删除多少xp 的+/- 数量。然后这些信号可以添加一个新的UserExpRecord 给用户xp。让UserExpRecord 发出一个保存信号,通知用户模型它需要重新收集(SUM)与用户相关的所有 xp 记录并将该值保存给用户。

    这使您可以立即获得为用户添加 xp 的时间和数量的记录。第二个好处是您可以避免任何类型的竞争条件,因为您没有尝试锁定表行并增加值。

    也就是说,根据您的后端,可能会有一个原子线程安全的“upsert”或“increment”函数,允许您在事务中安全地递增一个值,同时阻止所有其他写入。这将允许写入正确堆叠。我相信第一个解决方案(单独的 xp 记录)会比这个或您当前的解决方案(缺少/丢失 xp 更新的未知竞争条件)带来更小的头痛(缓存失效)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-09-24
      • 2013-08-18
      • 1970-01-01
      • 1970-01-01
      • 2012-10-21
      • 2012-11-26
      • 2015-09-20
      相关资源
      最近更新 更多