Skip to main content
 首页 » 编程设计

python之Django之如何处理并发

2024年08月28日29zlslch

我目前正在开发一款游戏,用户可以在其中获得经验值。我的(自定义)用户模型看起来像这样:

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 增益将覆盖所有其他 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 中锁定一个对象?有没有办法告诉一个对象需要重新加载?有没有更好的方法来完成这个(也许使用某种中间件?)。

请您参考如下方法:

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

这为您提供了记录何时以及为用户添加了多少 xp 的直接好处。第二个好处是您可以避免任何类型的竞争条件,因为您不会尝试锁定表行并增加值。

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