List<T>并发删除和添加

14

我不太确定,所以想询问一下。从一个System.Collections.Generic.List<>对象中移除和添加项目是否是非线程安全的?

我的情况:

当连接被接收时,它会被添加到列表中,但与此同时,有一个工作程序会删除死掉的连接等。

这样会有问题吗?需要使用lock吗? 我还想知道是否可以在列表对象上使用锁定及其Foreach<>方法。


请访问https://dev59.com/n2w15IYBdhLWcg3wcrdC - 其答案值得一读。 - Bernhard Hofmann
5个回答

32

是的,对于一个 List<>,添加或移除元素是不线程安全的,所以需要同步访问,例如使用 lock

请注意,lock 关键字并不锁定你用作标识符的对象,它只是防止两个线程同时进入同一代码块。你需要在访问列表的 所有 代码周围加锁,并使用 相同的对象 作为标识符。


1
@AJ:是的,你需要在对列表的所有访问周围加锁。如果你正在遍历列表,而另一个线程删除了一个项目,你将会得到一个异常(或在某些情况下崩溃)。 - Guffa
@Guffa是这样的一个并发问题:添加的项目可能不在我们预期的索引中,如果我不关心我的项目顺序,我是否仍然需要锁定列表? - eran otzap
@eranotzap:不,并发问题出现在List对象的内部变量中。如果两个线程同时添加项目,则由于List类中的代码未考虑多个线程,因此内部变量可能会被破坏。因此,在多个线程更改列表时,您始终需要同步代码。 - Guffa
@Guffa 如果一个线程添加,多个线程可能删除的情况怎么办? - eran otzap
@eranotzap:我也是这样。线程所做的更改种类并不重要。 - Guffa
显示剩余2条评论

4
在这个问题提出的时候,.NET Framework 4 还不存在,但是现在遇到这个问题的人应该尝试使用 System.Collections.Concurrent 命名空间中的集合来处理线程安全问题。

2

List<T> 不是线程安全的,因此您需要使用锁来控制对列表的访问。如果有多个线程访问该列表,请确保它们都尊重锁,否则您将遇到问题。最好的方法是子类化List,以便自动发生锁定,否则您很可能最终会忘记。


那不是最好的方法。这不仅仅是因为List的实现 - 它的接口也没有设计成可以被多个线程使用。如果你想要一种线程安全的方法来做这件事,你需要完全不同的接口。List应该是一个内部实现细节,对用户来说应该是很好隐藏的。http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx - Mark Byers

0

在特定的代码中使用锁确实可以使其线程安全,但我不认为这适用于当前的情况。

您可以实现Synchronized方法来使集合线程安全。这个link解释了为什么以及如何做到这一点。

另一种纯编程方法在link中提到,虽然我从未亲自测试过,但应该可以工作。

顺便说一下,更大的问题之一是,您是否试图自己维护类似连接池的东西?如果是,为什么?

我收回我的答案。使用锁比使用这种方法更好。


Synchronized 是一个不好的想法。请参见 http://blogs.msdn.com/b/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx - David White
1
我点赞是因为“你收回了”:)而没有删除,这是你可以做的。但是留下它可能对其他人有启示。 - Evgeniy Berezovsky

-3

实际上,有时List<>是线程安全的,有时不是,根据Microsoft:

此类型的公共静态成员是线程安全的。任何实例成员都不能保证是线程安全的。

但该页面继续说:

枚举集合本质上不是线程安全的过程。在罕见的情况下,其中一个枚举与一个或多个写访问竞争,确保线程安全的唯一方法是在整个枚举期间锁定集合。为了允许多个线程对集合进行读写访问,必须实现自己的同步。


5
不,列表(List)从来都不是线程安全的。这只是适用于所有常规类的标准文本。由于列表(List)类根本没有任何静态成员,因此也不存在线程安全的成员。 - Guffa
1
@Guffa:我非常确定在存在多个读取器和零个写入器的情况下,List<T>是线程安全的。集合不在存在多个读取器的情况下是线程不安全的情况非常罕见,因此在缺乏其他文档说明的情况下,这种程度的线程安全性被认为是默认的;尽管如此,我会说“永远”不是线程安全的集合只有像延迟集合这样的东西,其中两个同时进行的“读取”访问可能会导致冲突修改。 - supercat
@supercat:你没有抓住重点。如果你从不改变列表,线程安全就不是问题,但这并不意味着该列表有时是线程安全的。该问题涉及的操作从来都不是线程安全的。 - Guffa
@Guffa:某些类型的集合可能会在两个线程同时尝试读取它们时失败,无论是否有任何线程尝试写入它们。List<T>集合保证一个线程的读取不会以任何干扰另一个状态的读取的方式改变其状态。事实上,在当前的实现中,读取不会改变其状态,但是将来的实现理论上可以通过设置“已删除”标志并推迟实际删除直到下一次读取较晚的项来实现Remove。这样做的实现将需要... - supercat
@Guffa:语句“在List(Of T)上执行多个读操作是安全的”暗示了在某些情况下具有一定的线程安全性,这与“List<T>从不是线程安全的”这一说法相矛盾。 - supercat
显示剩余2条评论

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