从Lookup<TKey, TElement>中读取多个线程是否安全?

5
多个线程从 Lookup<TKey, TElement> 中读取是否安全? Lookup<TKey, TElement> 是不可变的,但是MSDN指出:

该类型的任何公共静态成员(在 Visual Basic 中为 Shared)都是线程安全的。任何实例成员都不能保证是线程安全的。

虽然我恐惧地想象着,但我想知道 MSDN 文档生成机器是否可能是错误的。

3
抱歉,我忘了这里不允许有乐趣。 - Ronnie Overby
5
当然可以。某物可以在外部保持不可变,但在内部改变状态(例如进行缓存)——在这种情况下,通过公共API进行读取的两个线程 可能 潜在地破坏内部状态。 - Jon Skeet
2
@RonnieOverby:我不确定。我强烈怀疑它是线程安全的,但我不想保证。还有其他原因可能导致它在读取时不是线程安全的,比如内存模型从某些线程中给出“陈旧”的读取。 - Jon Skeet
1
@Jon Skeet - 如果你问我,这听起来像是一段糟糕的代码。 :) - ChaosPandion
我想知道你在哪里找到了关于线程安全的评论,因为我在你提供的链接中没有找到相关内容:http://msdn.microsoft.com/en-us/library/bb460184.aspx - Tim Schmelter
显示剩余5条评论
4个回答

2

因为我不想冒险可能在一年后需要调试一个晦涩的多线程相关的错误,所以我假设在没有手动同步的情况下使用这个类是不安全的。


1
只要没有写入操作,仅进行读取操作是线程安全的。这在任何情况下都是有效的。
从某种意义上说,您的问题与线程安全的概念是正交的。写入操作与写入或读取操作结合使用时不是线程安全的,但多次读取而没有写入操作是线程安全的。
MSDN关于实例成员不能保证线程安全的说法只有在非线程安全场景下才能成立,这种场景的定义就意味着存在写入操作。

2
只要没有写入操作,仅进行读取是线程安全的。这在任何情况下都是有效的。——这并不正确。请考虑内存模型——例如,一个线程可能会在能够“看到”所有条目的数据之前“看到”查找。这取决于内存屏障的设置方式。 - Jon Skeet
@Tudor - 是的,这是真的,我在写我的定义时有意思考虑到了这一点。但是,我宁愿晚上睡觉,也不想担心他们在编程ConcurrentDictionary<TK,TV>BlockingCollection<T>等时是否做得正确...这更加说明了为什么我不想相信这个类是线程安全的,因为他们甚至没有建议在编程时考虑并发性。 - Ronnie Overby
@JonSkeet或其他人,这是一个旧话题,但您能否详细说明一下“例如,线程在能够查看所有条目的数据之前可能会‘看到’Lookup。这取决于内存屏障的设置。”的含义?我的用例是一个单例缓存类,它使用System.Lazy的默认线程安全性为所有调用线程实例化单个Lookup类。然后,每个调用线程都不会写入基础集合或其内部值,只读取。那肯定是安全的,对吧? - Jordan Rieger
@JordanRieger:我可能需要看真正的代码才能确定——但说实话,实际实现的内存模型比保证的要强得多。我猜你应该没问题。 - Jon Skeet
@JonSkeet 谢谢!是的,我认为我没问题,特别是因为我确保传入 Lookup 构造函数的集合是一个 List,在此之前由单个线程(通过 System.Lazy)完全实例化,并且在此之后不会直接读取或写入。 - Jordan Rieger
显示剩余8条评论

0

这是标准免责声明,适用于大多数类,你可能已经注意到了。一些方法可能是线程安全的,但是“不能保证”。

通常情况下,如果没有写入集合,使用多个线程从集合中读取是安全的。如果您需要同时更新集合,请使用适当的同步或内置的线程安全集合,例如SynchronizedKeyedCollection


并非所有的类都有此免责声明。请查看 Dictionary<TK,TV>BlockingCollection<T> 或任何 Concurrentxxxx<T> 中的一个。它们会告诉您,这些对象的某些或全部交互是安全的,可以在多个线程中使用。 - Ronnie Overby
谢谢。将“全部”替换为“大多数”。 - Alexei Levenkov

0

因为 Lookup<TKey,TElement> 是不可变的,这意味着您将为所有成员获取相同的值。 这并不意味着存储在其中的项目不能被修改。 因此,该集合实际上不是线程安全的。一个完美的例子是,大多数linq是惰性评估的,并且创建枚举器可能涉及执行惰性代码。 尝试在两个单独的线程中进行枚举可能会导致集合实现两次,从而产生错误的结果。

更新: 现在源代码可以在https://referencesource.microsoft.com 上获得,确认了在方法调用期间设置内部状态时不考虑多线程问题,这意味着可能存在竞争条件,Lookup<TKey,TElement> 类实际上是不安全的。


1
ToLookup 不是惰性的,它创建了一个不可变的集合。它的项目不是线程安全的事实并不能证明该集合不是线程安全的。否则,在 concurrent namespace 中的所有集合也都不是线程安全的。 - Tim Schmelter
1
如果您查看文档,就不再提到它了。 - Tim Schmelter
在新文档中,如果您查看像Skip这样的方法,您会发现它基本上说的是我的答案所说的。 - Charles Lambert
ToLookup并不是延迟加载,所以我不知道它与此有何关系。这甚至不是关于ToLookup方法,而是关于其结果,集合内部的项可以被修改并不重要。您无法在不创建新对象的情况下突变此集合,因此该集合本身是线程安全的,至少“大体上”(请阅读Jon Skeet的评论)。 - Tim Schmelter
一旦创建了它的实例(仅通过ToLookup工作,因为没有构造函数),它就不会被修改。但是,我也不能保证在所有情况下查找的线程安全性。 - Tim Schmelter
显示剩余2条评论

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