【发布时间】:2018-05-24 17:01:47
【问题描述】:
我有一个ConcurrentDictionary 存储Items:
ConcurrentDictionary<ulong, Item> items;
现在我想锁定这本字典中的 Item,以便我可以安全地对其进行操作。
这段代码正确吗?
try
{
Item item;
lock(item = items[itemId])
{
// do stuff
// possibly remove itemId from the dictionary
}
}
catch(KeyNotFoundException)
{
// ...
}
我担心的是这个。我想lock(item = items[itemId])可以分解成两个操作:
- 将
items[itemId]的引用分配给item - 锁定
item
不一定是原子的。
所以,我担心以下竞争条件:
- 线程 1 执行
item = items[itemId],但尚未执行lock(item) - 线程 2 对
itemId的相同值执行lock(item = items[itemId]) - 线程 2 从
items中删除itemId - 线程 2 释放它的锁
- 线程 1 执行
lock(item),不知道itemId已不在字典中 - 线程 1 错误地在
item上运行,而不是按应有的方式转到其catch块。
以上分析正确吗?
在这种情况下,以这种方式修改我的代码就足够了吗?
try
{
Item item;
lock(items[itemId])
{
item = items[itemId];
// do stuff
// possibly remove itemId from the dictionary
}
}
catch(KeyNotFoundException)
{
// ...
}
编辑:因为我开始假设我已经陷入了 XY 问题。这是背景。
多人国际象棋游戏。 itemId 是游戏的 ID。 item 是游戏的当前状态。 dict 包含正在进行的项目。该操作是处理玩家的移动,例如“来自e3的骑士到d1”。如果由于玩家的移动游戏完成,那么我们return the final state of the game 并从字典中删除游戏。当然,在已完成的游戏上执行任何进一步的移动都是无效的,因此是 try/catch 块。
try/catch 块应该可以正确检测以下情况:
- 玩家发送无效命令,命令在不存在的游戏中移动;
- 由于网络延迟,玩家在有效游戏中进行移动的命令会在他们的计时器用完后到达服务器。
【问题讨论】:
-
ConcurrentDictionary的重点不就是你不需要需要锁语句吗? -
@maccettura AFAIK
ConcurrentDictionary意味着我可以从/到这个字典中自动检索/添加元素(所以我不必锁定整个字典来访问或添加元素)。但是如果我想对任何元素进行操作,我仍然需要锁定这个特定的元素。我错了吗? -
不仅可以检索,还可以从列表中添加/删除项目。 - 我会做以下使用
TryRemove检查返回值并使用out参数做一些事情,当你完成你的操作时,如果需要,重新添加它 -
@RandRandom 不确定我是否理解你。根据您的建议,当线程 1 对某个项目进行操作时,线程 2 不会错误地看到该项目不在字典中,而是正确地等待线程 1 完成?
-
既然你在做一个国际象棋游戏,怎么可能在一个游戏对象上同时发生 2 个或多个操作呢?国际象棋是回合制游戏,因此在任何给定时间只有一名玩家可以与游戏互动,因此基于该信息 - 我相信您所做的一切都是不必要的。所以,我更想说的是,当轮不到他们的时候,你的客户端不应该能够向服务器发送操作。
标签: c# multithreading locking race-condition