【问题标题】:.NET thread safety.NET 线程安全
【发布时间】:2010-08-23 01:42:28
【问题描述】:

List.Add 是一个实例成员。这意味着不能保证它是线程安全的。这是什么意思?

可能性 1. 如果两个线程调用 .Add 在不同的实例上,根据月相可能会出现意想不到的结果?

可能性 2. 如果两个线程在同一个实例上调用 .Add,根据月相可能会出现意想不到的结果,如果实例不同,则没有潜在问题。

可能性 3。Microsoft 根本不希望人们使用线程,因此他们将 .NET 编写为模棱两可。

【问题讨论】:

    标签: .net thread-safety


    【解决方案1】:

    可能性 1 并非如此。一个实例方法给其他实例造成问题是很不寻常的,以至于这会被清楚地记录下来(不仅有一个声明指出这一点,而且有一些理由,因为这通常是编码非常糟糕的标志,所以如果这是有充分理由的,有人会指出)。

    可能性 3 并非如此,因为他们刚刚记录了线程行为。

    可能性 2 是部分情况。但是,交互也可以是一个线程调用 Add 而另一个调用不同的非线程安全实例方法。

    框架提供的大多数可变类对于静态成员是线程安全的,对于实例方法是非线程安全的。这是有充分理由的。

    1. 如果静态方法不是线程安全的,则很难以线程安全的方式调用该方法,尤其是当该类可能被不同人编写的不同层代码使用时。这使得使这些方法成为线程安全的努力几乎总是合理的。无论如何,大多数此类成员也相对容易使线程安全(如果避免具有可变静态状态,这总是一件好事)。

    2. 单个对象的大量使用一次将由一个线程进行,而不会被另一个线程访问。这使得确保正确性的难度,如果出错则存在死锁的风险,以及对性能造成的开销,很难证明是合理的。使用该类的人相对也很容易确保由多个线程使用的实例以线程安全的方式使用。

    那里非常强调“相对”,因为编写线程安全代码并不总是那么容易。有时它很容易(不可变类需要一些工作才能使非线程安全!),但更多时候它非常困难(因此这里和其他地方关于该主题的许多问题)。

    然而,这正是在这种情况下应该将负担放在用户身上的原因。使这样一个类完全线程安全非常困难(实际上,有时被证明是不可能的)以至于结果对于大多数用户来说是不可接受的,他们是最有能力判断在特定情况下需要什么保护的人。

    【讨论】:

      【解决方案2】:

      我认为,List.Add 是一个不好的例子。 List.Remove 更好,因为实际上存在值得注意的线程问题。一个线程可能会尝试访问一个项目,而另一个线程会尝试对其调用List.Remove。现在,可能会在尝试访问该项目时将其删除,这会导致NullReferenceException。不过,总的来说,这主要是一个免责声明,因为没有适当的锁定机制。只要两个线程尝试访问同一个对象或同一段代码,请记住lock 以防止此类问题。

      【讨论】:

      • 是的,List.Remove 更好,这也让人想到了一个类似的概念,即为什么在使用该列表迭代器迭代同一个列表时不能从列表中删除一个项目。
      • List.Add 当然会涉及各种有趣的问题,尤其是涉及调整大小时。
      • 同样的问题适用于 Remove 以及 Add,因为它们都被描述为“不保证是线程安全的”。如果 Add 不应该引起关注,Microsoft 可能会以不同的方式记录 Add。
      【解决方案3】:

      因为没有在实例成员上实现锁定机制,所以他们将免责声明放在 MSDN 网站上。

      另见Statics and Thread Safety

      Statics and Thread Safety: Part II

      【讨论】:

        【解决方案4】:

        如果两个线程同时尝试对 List 的同一个实例执行不同的操作,则可能会发生不好的事情。多个线程可以安全地同时对列表执行不同操作的唯一情况是所有线程都在读取。我认为使用列表是安全的,尽管对于所有容器类可能不一定安全。

        .net 中的列表之类的集合一般存储在数组中;如果一个集合对于它的数组来说太大了,就会分配一个更大的数组。如果多个线程正在处理同一个集合并且没有任何互锁,则一个线程可能会添加一些会增长列表的内容,然后其他线程会尝试在列表出现的时间之间更改列表复制和集合开始使用新列表的时间。这可能会导致更改丢失,或导致其他不好的事情发生。

        【讨论】:

          【解决方案5】:

          如果两个不同的线程在没有同步/锁定的情况下修改同一个列表,这可能会导致问题。使用不同列表的两个线程会很好。大多数类也是如此——实际上很少有类明确声明“这个类是线程安全的。”,但如果你不在线程之间共享(访问)实例,几乎所有类都是安全的。如果一个类即使在线程不共享实例的情况下也会中断,文档会这样说——但这种情况太糟糕了,我希望 MS 将它排除在 API 之外。

          Microsoft 以他们的方式说话和做事(线程安全方面)有一个重要原因:

          线程安全很难。

          没有办法同步对每个人都有效的东西。并且在你不需要的时候锁定和同步会影响性能,甚至可能会影响稳定性。全方位的最佳解决方案是让代码只做它应该做的事情,没有同步,让需要线程安全的人自己安排。

          【讨论】:

          • 有趣的答案。这导致我考虑以下风险管理策略:如果我的结果很容易检查,那么我应该考虑多核计算机上的线程。如果我的结果很难检查,我应该集群单核计算机。
          • 没有那么糟糕。没有一种万能的解决方案。只要有任何特定功能正在使用它,您就需要同步对列表的访问——不要更短,最好不再使用。
          • @broiyan,集群单核计算机有其自身的问题。确实,如果您不能以一种相对容易处理线程问题的方式编写它(因为每个线程都有自己的对象),那么您将在不同的机器上遇到更大的问题。当线程“共享”时才会出现问题,但线程之间的共享比计算机之间的共享更容易(如果只是因为当计算机之间易于共享某些东西时,我们也可以在线程之间做同样的事情)。
          猜你喜欢
          • 2013-06-10
          • 2010-09-28
          • 2021-12-27
          • 2011-08-02
          • 2011-07-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-28
          相关资源
          最近更新 更多