什么是“线程安全”?(来自MSDN)

11
在许多 MSDN 文档中,这是在“线程安全”标题下编写的:
“此类型的任何公共静态成员(在 Visual Basic 中为 Shared)都是线程安全的。任何实例成员都不能保证是线程安全的。”
例如:这里可以找到一个例子: 连结 请问有人能够用简单易懂的方式解释一下吗?
谢谢 :)
4个回答

13

Eric Lippert在他的博客文章中有一篇关于这个问题的优秀文章。基本上,它本身没有太大意义。

就我个人而言,在这方面我不太信任MSDN,当我看到那些套话时。它并不总是说了算。例如,它也会同样关于Encoding说 - 尽管我们都在多个线程中使用编码。

除非我有任何理由认为不是这样(对于Encoding,我确实有理由这样认为),否则我假设可以从任何线程调用任何静态成员,而不会破坏全局状态。如果我想要从不同线程使用相同对象的实例成员,则假定只有一个线程将在任何时候使用该对象,通过锁定来确保这一点。(当然,并不总是这种情况。有些对象具有线程亲和性并且不喜欢即使在放置锁的情况下也被多个线程使用。UI控件是明显的例子。)

当然,如果对象被不明显地共享,那么它就会变得棘手 - 如果我有两个对象,它们各自共享对第三个对象的引用,则我可能会使用前两个对象独立于不同的线程,同时进行所有适当的锁定 - 但仍然会破坏第三个对象。

如果一个类型宣传自己是线程安全的,我希望它能提供一些详细信息。如果它是不可变的,那么很容易 - 您可以随意使用实例而不必担心它们。部分或完全“线程安全”的类型是可变的,其中详细信息非常重要。


5
微软的这个提示的意思是,“我们在实例方法中没有做太多/任何锁定[可能是出于性能方面的考虑]。所以如果你让十几个线程同时操作,然后出问题了,就不要来找我们哭诉了。这不是一个错误。我们已经提醒你了。” - cHao
1
@cHao:正确实现多线程远不止在实例方法上加锁这么简单。通常你需要让一系列方法调用作为一个原子操作 - 内部加锁也无济于事。遍历集合就是一个明显的例子。 - Jon Skeet
@Jon:同意,这还有很多需要考虑的地方。但是,如果任何重视稳定性的人都不会期望 MS 在多个方法调用之间内置原子性,因为这根本就没有多少额外的东西可以期望 MS 做得更好。 (这将需要 API 文档说明诸如“如果您调用 IsEmpty() 并且它返回 false,则必须调用 Remove() 或 ReleaseLock() 以避免死锁”的内容。)即使在单个方法中,如果不必要地使用它,也可能导致性能问题。因此 MS 没有理由去费心处理它。这就是我的观点。 - cHao
@cHao:我同意微软不尝试是正确的,但我对于“麻烦”这个想法持有异议。你的第一条评论暗示着仅锁定实例方法通常就足够了,而我对此表示怀疑。仅此而已。 - Jon Skeet
@Jon:顺便说一下,在你纠正之前,我的意思是“不使用锁定或不可变性”。 :) - cHao
显示剩余2条评论

5

您可以同时从多个线程访问该类的任何公共静态成员,而不会破坏类的状态。如果多个线程尝试同时使用实例方法(那些未标记为“static”的方法)访问对象,则该对象可能会损坏。

如果尝试同时从多个线程访问同一类实例不会导致问题,则该类是“线程安全”的。


4
一个对象被称为“线程安全”,意味着如果两个线程在(或者非常接近,在单CPU系统上)同一时间使用它,那么它不会被这些访问所破坏。通常通过获取和释放锁来实现,但是这可能会导致瓶颈,因此如果在不需要时进行锁定,则“线程安全”也可能意味着“慢”。
公共静态成员几乎可以预期在线程之间共享(注意,VB甚至称其为“Shared”),因此公共静态成员通常以安全使用的方式创建。
实例成员通常不是线程安全的,因为在一般情况下这会减缓速度。因此,如果您想要在线程之间共享一个对象,您需要自己进行同步/锁定。

0
为了理解这一点,请考虑以下示例。 在 .net 类 HashSet 的 MSDN 描述中,有一部分涉及线程安全性。在 HashSet 类的情况下,MSDN 表示:“此类型的任何公共静态成员(Visual Basic 中为共享)都是线程安全的。任何实例成员都不能保证是线程安全的。”当然,我们都知道竞争条件和死锁的概念,但微软想用简单的英语表达什么呢?如果两个线程向 HashSet 的“实例”添加两个值,则会出现某些情况,其中其计数为一。 在这种情况下,HashSet 对象已损坏,因为现在我们在 HashSet 中有两个对象,但其计数仅显示一个。但是,即使两个线程同时添加值,HashSet 的公共静态版本永远不会面临这种破坏。

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