List <t>:从不可变结构转变为可变结构</t>

时间:2011-07-28 13:06:23

标签: c# list collections thread-safety

我目前正在使用相当多的List,并且我经常通过foreach在这些列表上循环。
最初的List在启动时是不可动摇的。现在我需要在运行时从一个线程(一种监听器)修改List。我需要从对象A中的List中删除并添加到对象B的列表中.A和B是同一类的实例 不幸的是,没有同步列表。在这种情况下,你建议我做什么?在我的情况下,速度比同步更重要,因此我目前正在处理添加/删除列表的副本,以避免枚举器失败。
你有其他推荐的方法来解决这个问题吗?

class X {
    List<T> Related {get; set;}
}

在几个地方和不同的主题中,我正在使用

 foreach var x in X.Related

现在我需要基本上在另一个线程中执行

a.Related.Remove(t);
b.Related.Add(t);

为了避免潜在的异常,我目前正在这样做:

List<T> aNew=new List<T> (a.Related);
aNew.Remove(t);
a.Related=aNew;
List<T>bNew=new List<T>(b.Related){t};
b.Related=bNew;

这是否正确以避免例外?

3 个答案:

答案 0 :(得分:3)

来自此MSDN帖子:http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

“......确保线程安全的唯一方法是在整个枚举期间锁定集合。”

答案 1 :(得分:2)

考虑使用for循环并反向迭代您的集合。这样你就没有“枚举器失败”,并且当你向后收集你的集合时,它与循环的POV是一致的。

由于细节有限,很难讨论线程方面。

<强>更新

如果您的馆藏很小,并且您只有3-4“潜在的”并发用户,我建议使用@Jalal建议的普通锁定策略,尽管您需要向后迭代,例如。

private readonly object _syncObj = new object();

lock (_syncObj)
{
    for (int i = list.Count - 1; i >= 0; i--)
    {
        //remove from the list and add to the second one.
    }
}

您需要使用这些lock块来保护对列表的所有访问。

您当前的实现使用COW(写时复制)策略,该策略在某些情况下可能有效,但您的特定实现受到两个或多个线程获取副本,进行更改的事实的影响,但随后可能会覆盖其他线程的结果。

<强>更新

除了您的问题评论之外,如果您保证只有一个线程更新集合,那么您对COW的使用是有效的,因为多线程不会通过多线程覆盖来丢失更新和更新。它很好地利用了COW策略来实现无锁同步。

如果您携带其他线程来更新集合,我以前的锁定注释就会存在。

我唯一关心的是其他“读者”线程可能有原始列表地址的缓存值,并且在更新时可能看不到新地址。在这种情况下,请创建列表变量volatile

<强>更新

如果您确实采用无锁策略还有一个陷阱,设置a.Relatedb.Related之间仍然存在差距,在这种情况下,您的读者线程可能会迭代过时的收藏品,例如项目a可能已从list1中删除但尚未添加到list2中 - 项目a将不在任何列表中。您还可以在从list1中删除之前交换问题并添加到list2,在这种情况下,项目a将在两个列表中 - 重复。

如果一致性很重要,您应该使用锁定。

答案 2 :(得分:0)

您处理列表之前应该lock,因为您处于多线程模式,锁定操作本身不影响速度,lock操作以纳秒为单位执行10 ns取决于机器。所以:

private readonly object _listLocker = new object();

lock (_listLocker)
{
    for (int itemIndex = 0; itemIndex < list.Count; itemIndex++)
    {
        //remove from the first list and add to the second one.
    }
}

如果您使用框架4.0,我会让您使用ConcurrentBag代替列表。

修改:代码段:

List<T> aNew=new List<T> (a.Related);

如果只有与集合“包括添加删除替换项目”的所有交互以这种方式管理,这将有效。此外,您必须使用System.Threading.Interlocked.CompareExchangeSystem.Threading.Interlocked.Exchange方法将现有集合替换为新修改的集合。如果情况并非如此,那么你通过应对

无所作为

这不起作用。例如,考虑一个线程试图从集合中获取一个项目,同时另一个线程替换该集合。这可能会使项目在非常数据中被检索。还要考虑在你处理集合时,另一个线程想要在你处理的同时将项目插入集合中吗?
这将抛出异常表示集合已修改。

另一件事是你正在将整个集合应对到一个新的列表来处理它。当然这会损害性能,我认为使用lock这样的同步机制可以降低性能托盘,在处理多线程场景时,这是非常合适的事情。