锁(锁柜)和锁(variable_which_I_am_using)之间的区别

时间:2008-10-23 17:50:11

标签: c# .net multithreading

我正在使用C#& .NT 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上做了什么,如果有的话。

8 个答案:

答案 0 :(得分:28)

选项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实际上不是一种选择。

答案 1 :(得分:11)

重要的是要理解lock(m_Hash) NOT 阻止其他代码使用散列。它只能阻止其他代码运行,它也使用m_Hash作为锁定对象。

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

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

答案 2 :(得分:8)

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

答案 3 :(得分:3)

这不是你的“锁定”,它是锁定{...}之间的代码,这些代码很重要,而且你正在阻止它被执行。

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

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

答案 4 :(得分:2)

我认为你“传递”的变量的范围将决定锁的范围。 即一个实例变量将与该类的实例有关,而一个静态变量将用于整个AppDomain。

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

答案 5 :(得分:1)

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

通常我会选择OptionB来提供对m_Hash的线程安全访问。在OptionA中,我将用于锁定值类型,它不能与锁一起使用,或者我有一组需要同时锁定的对象,但我不知道如何使用{{1来锁定整个实例}}

答案 6 :(得分:0)

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

答案 7 :(得分:0)

只要在所有代码中使用OptionA,就可以访问m_hash,使用m_Locker来锁定它。

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