SynchronizedCollection<T>和其他并发集合之间有什么区别?

73

除了SynchronizedCollection<T>是一个类,而System.Collections.Concurrent命名空间中的并发集合是一个命名空间之外,SynchronizedCollection<T>和所有Concurrent Collections类都提供了线程安全的集合。我如何决定什么情况下使用其中之一,以及为什么?


1
看这里:https://dev59.com/A3I-5IYBdhLWcg3wQV8Z - StuartLC
2个回答

84
SynchronizedCollection<T> 类是在 .NET 2.0 中首次引入的,用于提供线程安全的集合类。它通过锁定来实现,因此您基本上拥有一个 List<T>,其中每个访问都包裹在 lock 语句中。 System.Collections.Concurrent 命名空间要新得多。它直到 .NET 4.0 才被引入,并包括一组更为完善和多样化的选择。这些类不再使用锁来提供线程安全性,这意味着它们在多个线程同时访问其数据的情况下应该能够更好地扩展。但是,这些选项中明显缺少实现 IList<T> 接口的类。
因此,如果您的目标是 .NET Framework 的版本 4.0,则应尽可能使用由 System.Collections.Concurrent 命名空间提供的集合之一。就像在选择 System.Collections.Generic 命名空间 中提供的各种类型的集合时一样,您需要选择最适合特定需求的功能和特性的集合。
如果您的目标是较早版本的 .NET Framework 或需要实现 IList<T> 接口的集合类,则必须选择 SynchronizedCollection<T> 类。
同时,这篇 MSDN 上的文章也值得一读:何时使用线程安全集合

因此,我可以在使用4.0版本时更喜欢System.Collections.Concurrent而不是SynchrinozedCollecction<T>! - Batrickparry
3
如果这些新的并发集合不使用锁来实现线程安全,那么并发性是如何实现的呢? - Matt
5
@Matt: 有多种方法。答案可能够复杂以至于值得提出一个新问题。但为了启发,可以看看这里:https://dev59.com/T3I-5IYBdhLWcg3wwbZL 和这里:https://dev59.com/Cm445IYBdhLWcg3wl7bW (另外,你读过我提供的MSDN文章吗?它简要介绍了他们在不使用锁的情况下使用的技巧,尽管可能不足以编写自己的实现。) - Cody Gray
1
谢谢提供的信息。那是一个很好的一小时左右,我读到了一些我之前甚至不知道在.NET中存在的东西(而且仍然没有完全理解)。 - Matt
7
@Matt,.NET4的并发类使用SpinWait对象来处理线程安全,而不是Monitor.Enter/Exit(也称为关键段)。区别在于SpinWait结构尝试保持线程运行并等待信号继续执行,而Critical section则进行了yield(即线程上下文切换),然后定期返回到线程并检查是否可以继续执行。CS非常快,但在某些情况下,如果受保护的代码执行速度很快,SpinWait会导致更好的性能。 - Lee Grissom
2
据我所知,至今仍没有并发的IList<T>替代品。在ConcurrentBag中,您无法真正删除项目。一些文章建议(滥用)ConcurrentDictionary,但由于其不必要的开销,我觉得这很烦人。因此,我自己继续使用SynchronizedCollection。 - Marco

3
"

SynchronizedCollection<T>

是一个同步的List<T>。这是一个可以在一秒钟内设计出来,并且可以在大约一个小时内完全实现的概念。只需将List<T>的每个方法包装在lock (this)中,就完成了。现在你有了一个线程安全的集合,可以满足多线程应用程序的所有需求。除了它并不是完美的。"

< p >在尝试使用SynchronizedCollection<T>进行任何非平凡操作时,它的缺点就会显现出来。特别是当您尝试将两个或多个集合方法组合为一个概念上的单一操作时,就会意识到该操作不是原子性的,并且不能通过隐式同步来实现原子性(需要锁定集合的SyncRoot属性),这破坏了集合的整个目的。以下是一些例子:

确保集合包含唯一元素:if (!collection.Contains(x)) collection.Add(x);。这段代码并不能起到确保的作用。ContainsAdd 之间的竞争条件会导致重复元素出现。
确保集合最多包含 N 个元素:if (collection.Count < N) collection.Add(x);CountAdd 之间的竞争条件可能会导致集合中存在超过 N 个元素。
"Foo" 替换为 "Bar"int index = collection.IndexOf("Foo"); if (index >= 0) collection[index] = "Bar";。当一个线程读取 index 时,它的值立即变得陈旧。另一个线程可能会以某种方式更改集合,使得 index 指向其他元素或超出范围。
此时您意识到,多线程比您最初想象的要求更高。在现有集合的 API 周围添加同步层是不够的。您需要一种从头开始设计用于多线程使用的集合,并具有反映此设计的 API。这就是引入 .NET Framework 4.0 中的并发集合的动机。
并发集合,例如ConcurrentQueue<T>ConcurrentDictionary<K,V>,是高度复杂的组件。它们比笨重的SynchronizedCollection<T>复杂得多。它们配备了特殊的原子API,非常适用于多线程环境(例如TryDequeueGetOrAddAddOrUpdate等),并且实现旨在在高负载下最小化争用。在内部,它们采用无锁、低锁和粒度锁定技术。学习如何使用这些集合需要一些研究。它们不是非并发对应项的直接替代品。
注意:枚举 SynchronizedCollection<T> 不是同步的。使用 GetEnumerator 获取枚举器是同步的,但使用枚举器是不同步的。因此,如果一个线程执行 foreach (var item in collection),而另一个线程以任何方式改变集合(例如 AddRemove 等),程序的行为是未定义的。安全地枚举 SynchronizedCollection<T> 的方法是获取集合的快照,然后枚举该快照。获取快照并不简单,因为它涉及两个方法调用(Count getter 和 CopyTo),因此需要显式同步。注意 LINQ ToArray 操作符本身不是线程安全的。下面是 SynchronizedCollection<T> 类的安全 ToArraySafe 扩展方法:
/// <summary>Copies the elements of the collection to a new array.</summary>
public static T[] ToArraySafe<T>(this SynchronizedCollection<T> source)
{
    ArgumentNullException.ThrowIfNull(source);
    lock (source.SyncRoot)
    {
        T[] array = new T[source.Count];
        source.CopyTo(array, 0);
        return array;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接