python - 如果我的资源更新方法还创建了不同类型的资源,它是否是线程安全的?

标签 python django tastypie

Tastypie 文档指出捆绑 keep Tastypie more thread-safe但没有解释如何以及在什么条件下。我浏览了 code但是我没有足够的经验来解决这个问题。

我正在制作一个游戏原型(prototype),该游戏有一个圆形对象(针对每一轮游戏),并且每一轮都有多个状态(针对该轮每个玩家的信息)。每个玩家通过对回合词组的回答来更新自己的状态。我需要一种机制,如果它不存在的话,可以懒惰地创建下一轮游戏。我目前会在玩家更新其状态时触发该回合的创建。

如果多个玩家同时更新他们的状态(参见StateResource.obj_update()) 那么他们创建下一轮的尝试是否会发生冲突?我认为如果一个 obj_update 调用检查下一轮是否存在并尝试在不同的 obj_update 完成创建下一轮之前创建下一轮,则可能会发生这种情况。我会用某种类型的互斥锁来解决这个问题,但我不确定是否有必要。我想知道是否有 Tastypie 方法来解决这个问题。

我的代码如下:

#models.py
class Round(models.Model):
    game_uid = models.CharField(max_length=75)
    word = models.CharField(max_length=75)
    players = models.ManyToManyField(User)
    next_round = models.OneToOneField('self',null=True,blank=True)

class PlayerRoundState(models.Model):
    player = models.ForeignKey(User)
    round = models.ForeignKey(Round)
    answer = models.CharField(max_length=75)

#api.py
class RoundResource(ModelResource):
    players = fields.ManyToManyField(UserResource, attribute='players',full=False)
    states = fields.ManyToManyField('wordgame.api.StateResource',
                                attribute='playerroundstate_set',
                                full=True)
    . . .
    def obj_create(self, bundle, request=None, **kwargs):
        bundle = super(RoundResource, self).obj_create(bundle, request,**kwargs)
        bundle.obj.word = choice(words) #Gets a random word from a list
        bundle.obj.round_number = 1
        bundle.obj.game_uid = bundle.obj.calc_guid() #Creates a unique ID for the game
        bundle.obj.save()
        return bundle

class StateResource(ModelResource):
    player = fields.ForeignKey(UserResource, 'player',full=False)
    round = fields.ForeignKey(RoundResource, 'round')
    . . . 
    def obj_update(self, bundle, request=None, skip_errors=False, **kwargs):
        bundle = super(StateResource, self).obj_update(bundle, request,
                                                   skip_errors, **kwargs)
        if bundle.obj.round.next_round is None:
            new_round = Round()
            new_round.word = choice(words)
            new_round.round_number = bundle.obj.round.round_number + 1
            new_round.game_uid = bundle.obj.round.game_uid
            new_round.save()
            for p in bundle.obj.round.players.all():
                new_round.players.add(p)
            new_round.save()
            bundle.obj.round.next_round = new_round
            bundle.obj.round.save()

        return bundle

最佳答案

我认为这与 Tastypie 没有太大关系。

您描述的问题与 ORM 和数据库有关。问题是,在某些情况下,这两个请求都可能创建新的 Round()(如果它们可以并行服务并随时切换,gunicorn 和 gevent 就是这种情况),并且其中一个会变得陈旧。

考虑以下情况:

第一个请求到达,检索当前回合并“看到”没有“下一个”回合。所以它执行:

new_round = Round()
new_round.word = choice(words)
new_round.round_number = bundle.obj.round.round_number + 1
new_round.game_uid = bundle.obj.round.game_uid
new_round.save()

与此同时,第二个请求到来并且(假设在您的设置中可能)处理切换到第二个请求。它还检索当前回合,并且它还“看到”没有下一轮,因此它也创建了一个(同一逻辑回合的第二个对象)。

然后处理切换回执行的第一个请求:

for p in bundle.obj.round.players.all():
    new_round.players.add(p)
new_round.save()
bundle.obj.round.next_round = new_round
bundle.obj.round.save()

所以现在"is"下一轮。第一个请求已处理,一切看起来都不错。 但是第二个请求还没有完成,它执行完全相同的操作,覆盖当前的轮次对象。

结果是您有一个过时的实例(第一个请求创建的 Round)并且第一组玩家使用与第二组不同的 Round .

这会导致数据库中的状态不一致。所以在这种情况下,您的资源更新方法不是线程安全的。

对此的一种解决方案是使用 select_for_update 从数据库中检索当前回合。参见 Django Docs .如果您使用它,第二个和连续的请求将等到您修改第一个请求中的当前回合,然后才从数据库中检索它。结果将是他们已经“看到”下一轮而不是尝试创建它。当然,您必须确保整个更新构成一个事务。

“使用”它的方法是覆盖 StateResource 资源中的 obj_get() 方法,而不是:

base_object_list = self.get_object_list(request).filter(**kwargs)

使用(未测试):

base_object_list = self.get_object_list(request).select_for_update().filter(**kwargs)

当然,这不是唯一的解决方案,但其他解决方案可能会涉及重新设计您的应用程序,因此这可能涉及较少。

关于python - 如果我的资源更新方法还创建了不同类型的资源,它是否是线程安全的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12100577/

相关文章:

python - 什么语法更适合 python != 或者不是?

python - LAN 上的 Django 开发服务器

Django modelform 不需要字段

restkit - RKPaginator 参数

python - 如何选择策略来减少过拟合?

python - 初始化空数组

python - 无法解决 WindowsError : [Error 2] The system cannot find the file specified

Django 和 Celery - 更改后将代码重新加载到 Celery 中

python - 是否可以使用 TastyPie 对 ToManyField 属性中的元素进行排序?

python - 如何使用 tastypie 在 json 中添加额外的消息?