Django 1.6 事务以避免竞争条件
我正在尝试使用Django 1.6的事务功能,以避免我正在开发的游戏中的竞争条件。这个游戏服务器的目标很简单:就是把两个玩家配对在一起。
我现在的做法是:
- 用户想要玩游戏。
- 服务器检查是否有其他人也在等待玩游戏。
- 如果没有,它就创建一个GameConnection对象(这个对象有一个唯一的标识符 - uuid4)。
- 如果有,它就获取这个GameConnection的标识符,并删除这个GameConnection。
这段代码是:
# data['nickname'] = user's choice
games = GameConnection.objects.all()
if not games:
game = GameConnection.objects.create(connection=unicode(uuid.uuid4()))
game.nick1 = data["nickname"]
game.save()
response = HttpResponse(json.dumps({'connectionId': game.connection, 'whoAmI': 1, 'nick1': game.nick1, 'nick2': ""}))
else:
game = games[0]
conn = game.connection
nick1 = game.nick1
nick2 = data["nickname"]
game.delete()
response = HttpResponse(json.dumps({'connectionId': conn, 'whoAmI': 2, 'nick1': nick1, 'nick2': nick2}))
return response
显然,上面的代码存在竞争条件。因为这段代码不是原子操作,所以可能会出现以下情况:
- A检查游戏连接,发现没有。
- A创建了一个游戏连接。
- B检查游戏连接,发现有一个(A)。
- C检查游戏连接,发现有一个(A)。
- B获取了A的连接标识符并开始了游戏。
- C也获取了A的连接标识符并开始了游戏。
我尝试把整个代码块放在with transaction.atomic():
下面,或者使用@transaction.atomic
装饰器。但我仍然能重现这个竞争条件。
我确信在事务的动态处理上我遗漏了什么。有没有人能帮我解答一下?
1 个回答
2
@Sai 说得对……关键在于,锁或互斥量(mutex)不会在写入(或删除)之前发生。按照现在的代码,总会有一个时间段是在“发现”(读取)待处理连接和“声明”(写入/锁定)待处理连接之间,这个时候无法知道连接是否正在被声明。
如果你使用的是 PostgreSQL(我很确定 MySQL 也支持这个),你可以通过“select for update”来强制锁定,这样在事务完成之前,其他请求就无法获取同一行的数据:
game = GameConnection.objects.all()[:1].select_for_update()
if game:
#do something, update, delete, etc.
else:
#create
最后一点 - 考虑使用其他方法,而不是 all()
,这样可以明确指定可能被选择的游戏(例如,可以按“创建”时间戳排序或其他方式)。希望这对你有帮助。