同时迭代并对泛型集合执行操作

时间:2013-09-12 16:54:39

标签: c# multithreading list collections

我使用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);
 }

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

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

3 个答案:

答案 0 :(得分: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.
  }
}

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

但是,根据您的更新中的上下文线索,我认为此集合将会很小。似乎每个“游戏客户端”只有一个条目,这意味着它可能会存储少量项目吗?如果这是正确的,那么GetEnumerator方法的表现将是无关紧要的。

您还应该考虑ConcurrentDictionary。我注意到您正在尝试根据zoneId匹配集合中的项目。如果您将项目存储在由ConcurrentDictionary键入的zoneId中,那么您根本不需要迭代该集合。当然,这假设每zoneId只有一个条目,可能不是这种情况。

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

  • 迭代可以是无锁的。
  • 根据您的代码添加和删除项目似乎不常见。

您可以使用几种模式使列表复制策略工作得非常好。我在答案herehere中谈到了这些问题。