0

我使用 c# async socks 创建了一个游戏模拟程序。我需要同时删除/添加和对集合(包含客户端的列表)进行迭代。我目前正在使用“锁定”,但是,这是一个巨大的性能下降。我也不想使用“本地列表/副本”来保持列表是最新的。我听说过“ConcurrentBags”,但是,我不确定它们对于迭代的线程安全性(例如,如果一个线程从列表中删除一个元素,而另一个线程正在对其进行迭代!?)。

你有什么建议?

编辑:这是一种情况 ,即数据包被发送给房间中的所有用户

lock (parent.gameClientList)
{
    for (int i = 0; i <= parent.gameClientList.Count() - 1; i++) if (parent.gameClientList[i].zoneId == zoneId) parent.gameClientList[i].SendXt(packetElements); //if room matches - SendXt sends a packet
}

当新客户端连接时

 Client connectedClient = new Client(socket, this);
 lock (gameClientList)
 {
     gameClientList.Add(connectedClient);
 }

客户端断开连接时的情况相同。

我要求一个更好的选择(性能方面),因为锁会减慢一切。

4

3 回答 3

1

听起来问题在于您正在 foreach 循环中完成所有工作,并且它锁定添加/删除方法的时间过长。解决这个问题的方法是在集合被锁定时快速创建一个副本,然后您可以关闭锁定并迭代副本。

Thing[] copy;
lock(myLock) {
  copy = _collection.ToArray();
}
foreach(var thing in copy) {...}

缺点是当您开始对该副本的某个对象进行操作时,它可能已从原始集合中删除,因此您可能不想再对其进行操作。那是另一件事,您只需要弄清楚要求即可。如果这是一个问题,一个简单的选择是锁定循环的每次迭代,这当然会减慢速度,但至少它不会在循环运行的整个持续时间内锁定:

foreac(var thing in copy) {
  lock(myLock) {
    if (_collection.Contains(thing)) //check that it's still in the original colleciton
      DoWork(thing); //If you can move this outside the lock it'd make your app snappier, but it could also cause problems if you're doing something "dangerous" in DoWork.
  }
}

如果这就是您所说的“本地副本”,那么您可以忽略此选项,但我想我会提供它以防您有其他意思。

于 2013-09-12T17:21:55.123 回答
1

每次您同时做某事时,您都会因任务管理(即锁定)而遭受损失。我建议你看看你的过程中的瓶颈是什么。您似乎有一个共享内存模型,而不是消息传递模型。如果您知道需要一次修改整个集合,则可能没有好的解决方案。但是,如果您要按特定顺序进行更改,则可以利用该顺序来防止延迟。锁是悲观并发的一种实现。您可以切换到乐观并发模型。一种成本是等待,另一种成本是重试。同样,实际解决方案取决于您的用例。

于 2013-09-12T18:15:51.297 回答
0

问题ConcurrentBag在于它是无序的,因此您不能像当前那样按索引拉出项目。但是,您可以通过迭代它foreach以获得相同的效果。此迭代将是线程安全的。如果在迭代发生时添加或删除项目,它不会变坏。

不过还有另一个问题ConcurrentBag。它实际上将内容复制到一个新的List内部以使枚举器正常工作。因此,即使您只想通过枚举器选择一个项目,由于枚举器的工作方式,它仍然是一个 O(n) 操作。您可以通过拆卸它来验证这一点。

但是,根据您更新的上下文线索,我认为这个集合会很小。似乎每个“游戏客户端”只有一个条目,这意味着它可能会存储少量项目,对吧?如果这是正确的,那么该GetEnumerator方法的性能将几乎是微不足道的。

你也应该考虑ConcurrentDictionary。我注意到您正在尝试匹配基于zoneId. 如果您将项目存储在ConcurrentDictionarykeyed byzoneId那么您根本不需要迭代集合。当然,这假设每个条目只有一个,zoneId但情况可能并非如此。

您提到您不想使用“本地列表/副本”,但您从未说明原因。我认为您应该出于以下原因重新考虑这一点。

  • 迭代可以是无锁的。
  • 添加和删​​除项目似乎是代码中不常见的基于上下文的线索。

您可以使用几种模式来使列表复制策略非常有效。我在这里这里的回答中谈到了它们。

于 2013-09-12T20:37:21.140 回答