【问题标题】:Difference between lock(locker) and lock(variable_which_I_am_using)lock(locker) 和 lock(variable_which_I_am_using) 的区别
【发布时间】:2008-10-23 17:50:11
【问题描述】:

我正在使用 C# 和 .NET 3.5。 OptionA 和 OptionB 有什么区别?

class MyClass
{
    private object m_Locker = new object();
    private Dicionary<string, object> m_Hash = new Dictionary<string, object>();

    public void OptionA()
    {
        lock(m_Locker){ 
          // Do something with the dictionary
        }
    }

    public void OptionB()
    {
        lock(m_Hash){ 
          // Do something with the dictionary
        }
    }       
}

我开始涉足线程(主要是为多线程应用程序创建缓存,不使用 HttpCache 类,因为它没有附加到网站),我在很多地方都看到了 OptionA 语法我在网上看到的示例,但我不明白通过 OptionB 完成的原因(如果有的话)。

【问题讨论】:

    标签: c# .net multithreading


    【解决方案1】:

    选项 B 使用要保护的对象来创建临界区。在某些情况下,这更清楚地传达了意图。如果一致使用,它保证一次只有一个受保护对象的关键部分处于活动状态:

    lock (m_Hash)
    {
        // Across all threads, I can be in one and only one of these two blocks
        // Do something with the dictionary
    }
    lock (m_Hash)
    {
        // Across all threads, I can be in one and only one of these two blocks
        // Do something with the dictionary
    }
    

    选项 A 限制较少。它使用辅助对象为要保护的对象创建临界区。如果使用多个次要对象,则可能有多个受保护对象的临界区一次处于活动状态。

    private object m_LockerA = new object();
    private object m_LockerB = new object();
    
    lock (m_LockerA)
    {
        // It's possible this block is active in one thread
        // while the block below is active in another
        // Do something with the dictionary
    }
    lock (m_LockerB)
    {
        // It's possible this block is active in one thread
        // while the block above is active in another
        // Do something with the dictionary
    }
    

    如果您只使用一个辅助对象,则选项 A 等效于选项 B。就阅读代码而言,选项 B 的意图更加清晰。如果您要保护多个对象,则选项 B 并不是一个真正的选项。

    【讨论】:

    • 可能可以使用 OptionB,因为 m_Hash 是私有的。 OptionA 更常见,因为通常认为锁定非私有/您分发引用的对象是危险的。 (在编写代码时)可能很难或不可能跟踪该实例可能结束的位置,因此还有谁可能锁定它。 (如果您真的想发送对象并可能将其锁定在其他类中,那么您可能应该围绕您的对象编写一个线程安全的包装器并共享一个包装器实例。)
    【解决方案2】:

    了解 lock(m_Hash) 确实不会阻止其他代码使用散列是很重要的。它只会阻止其他也使用 m_Hash 作为其锁定对象的代码运行。

    使用选项 A 的一个原因是因为类可能有私有变量,您将在 lock 语句中使用这些变量。只使用一个对象来锁定对所有对象的访问,而不是尝试使用更细粒度的锁来锁定对您需要的成员的访问,这要容易得多。如果您尝试使用更细粒度的方法,在某些情况下您可能需要使用多个锁,然后您需要确保始终以相同的顺序使用它们以避免死锁。

    使用选项 A 的另一个原因是,对 m_Hash 的引用可能会在您的类之外访问。也许您有一个公共属性提供对它的访问,或者您将其声明为受保护并且派生类可以使用它。在任何一种情况下,一旦外部代码引用了它,外部代码就有可能将它用于锁定。这也增加了死锁的可能性,因为您无法控制或知道锁定的顺序。

    【讨论】:

      【解决方案3】:

      实际上,如果您正在使用它的成员,那么锁定对象并不是一个好主意。 Jeffrey Richter 在他的“CLR via C#”一书中写道,不能保证您用于同步的一类对象在其实现中不会使用lock(this)(这很有趣,但这是微软推荐的同步方式有一段时间...然后,他们发现这是一个错误),因此使用特殊的单独对象进行同步总是一个好主意。因此,如您所见,OptionB 不会为您提供死锁保证 - 安全性。 所以,OptionA 比 OptionB 安全得多。

      【讨论】:

      • 你实际上并没有回答这个问题,它涉及使用this以外的对象的两种方式。
      • 可能是我的回答不够清楚,我稍微修改了一下。
      【解决方案4】:

      这不是您要“锁定”的内容,而是包含在 lock { ... } 之间的代码,这很重要,并且您正在阻止执行。

      如果一个线程在任何对象上取出 lock(),它会阻止其他线程获得同一对象上的锁,从而阻止第二个线程执行大括号之间的代码。

      这就是为什么大多数人只是创建一个垃圾对象来锁定,它会阻止其他线程获得对同一个垃圾对象的锁定。

      【讨论】:

      • 我看不出第二和第三段是如何跟在第一段之后的,它声称你锁定的内容并不重要。
      【解决方案5】:

      我认为你“传递”的变量的范围将决定锁的范围。 即,实例变量将与类的实例相关,而静态变量将针对整个 AppDomain。

      查看集合的实现(使用反射器),该模式似乎遵循一个名为 SyncRoot 的实例变量被声明并用于与集合实例相关的所有锁定操作。

      【讨论】:

        【解决方案6】:

        嗯,这取决于你想锁定什么(设为线程安全)。

        通常我会选择 OptionB 来仅提供对 m_Hash 的线程安全访问。其中作为 OptionA,我将用于锁定值类型,它不能与锁一起使用,或者我有一组需要同时锁定的对象,但我不使用 lock(this) 锁定整个实例

        【讨论】:

          【解决方案7】:

          锁定您正在使用的对象只是为了方便。外部锁对象可以使事情变得更简单,如果共享资源是私有的,例如集合(在这种情况下您使用ICollection.SyncRoot 对象),也需要外部锁对象。

          【讨论】:

            【解决方案8】:

            只要在你的所有代码中,OptionA 就是去这里的方式,当你访问 m_hash 时,你使用 m_Locker 来锁定它。

            现在想象一下这种情况。你锁定对象。您调用的其中一个函数中的那个对象有一个lock(this) 代码段。在这种情况下,这是一个肯定无法恢复的死锁

            【讨论】:

              猜你喜欢
              • 2014-05-18
              • 2015-03-21
              • 2017-12-07
              • 1970-01-01
              • 2016-10-07
              • 1970-01-01
              • 1970-01-01
              • 2016-08-16
              • 1970-01-01
              相关资源
              最近更新 更多