Tastypie文档指出捆绑keep Tastypie more thread-safe但不解释如何以及在什么条件下。我已经浏览了code但是我没有经验足以绕过它。
我正在制作一个游戏,其中包含一个圆形对象(每轮游戏)以及每轮的多个状态(对于该轮的每个玩家信息)。每个玩家都会通过回答单词短语来更新自己的状态。如果它尚不存在,我需要一种懒惰创造下一轮游戏的机制。当玩家更新状态时,我目前会触发该轮次创建。
如果多个玩家同时更新其状态(请参阅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
答案 0 :(得分:0)
我认为这与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()
所以现在“是”下一轮。处理第一个请求,一切都很好看。 但第二个请求尚未完成,它执行完全相同的操作,覆盖当前的圆形对象。
结果是您有一个陈旧的实例(第一个请求创建的Round
),并且第一组玩家使用与第二组不同的Round
。
这会导致数据库中的状态不一致。因此,在这种情况下,您的资源更新方法是 NOT 线程安全。
对此的一个解决方案是使用select_for_update
从数据库中检索当前轮次。见Django Docs。如果您使用了它,则第二个和连续的请求将等到您在第一个请求中修改当前轮次,然后才从数据库中检索它。结果是他们已经“看到”下一轮而不是试图创造它。当然,您必须确保整个更新构成一次交易。
“使用”它的方法是覆盖obj_get()
资源中的StateResource
方法,而不是:
base_object_list = self.get_object_list(request).filter(**kwargs)
使用(尚未测试):
base_object_list = self.get_object_list(request).select_for_update().filter(**kwargs)
当然,这不是唯一的解决方案,但其他人可能会涉及重新设计您的应用程序,因此这可能只会减少参与。