Iterate and do operations on a generic collection concurrently
我用C异步袜子创建了一个游戏模拟程序。我需要同时删除/添加集合(保存客户端的列表)上的&do迭代。我目前使用的是"锁",不过,这是一个巨大的性能下降。我也不想使用"本地列表/副本"来保持列表的最新。我听说过"ConcurrentBags",但是,我不确定它们对于迭代有多安全(例如,如果一个线程从列表中删除了一个元素,而另一个线程正在对其进行迭代!)?)
你有什么建议?
编辑:这里有一种情况当一个包被发送到一个房间中的所有用户时
1 2 3 4 | 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 } |
当新客户机连接时
1 2 3 4 5 | Client connectedClient = new Client(socket, this); lock (gameClientList) { gameClientList.Add(connectedClient); } |
号
客户机断开连接时的情况相同。
我要求一个更好的选择(性能方面),因为锁会减慢一切。
每次您同时执行某项操作时,都会因任务管理(即锁)而丢失。我建议您看看您的流程中的瓶颈是什么。您似乎有一个共享内存模型,而不是消息传递模型。如果您知道需要立即修改整个集合,那么可能没有一个好的解决方案。但是,如果您在某个特定的顺序中进行更改,那么您可以利用该顺序来防止延迟。锁是悲观并发的一种实现。您可以切换到乐观并发模型。一方面成本在等待,另一方面成本在缩减。同样,实际的解决方案取决于您的用例。
听起来问题在于,您在foreach循环中完成了所有工作,并且锁定添加/删除方法的时间太长。解决这个问题的方法是在集合被锁定时快速复制它,然后您可以关闭锁并在副本上迭代。
1 2 3 4 5 | Thing[] copy; lock(myLock) { copy = _collection.ToArray(); } foreach(var thing in copy) {...} |
缺点是,当您开始操作该副本的某个对象时,它可能已从原始集合中删除,因此您可能不想再对其进行操作。这是另一件事,你只需要找出需求。如果这是一个问题,一个简单的选项是锁定循环的每个迭代,当然这会减慢速度,但至少在循环运行的整个过程中不会锁定:
1 2 3 4 5 6 | 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. } } |
号
如果这就是你所说的"本地副本",那么你可以忽略这个选项,但我想我会提供它,以防你意味着其他事情。
不过,
但是,根据您更新的上下文线索,我认为这个集合将很小。似乎每个"游戏客户端"只有一个条目,这意味着它可能会存储少量的项目,对吗?如果这是正确的,那么
你也应该考虑
你提到你不想使用"本地列表/副本",但你从来没有说原因。我认为你应该重新考虑一下原因。
- 迭代可以是无锁的。
- 添加和删除项目似乎是代码中不常见的基于上下文的线索。
有两种模式可以让列表复制策略工作得非常好。我在这里和这里的答案中都谈到了它们。