关于锁定对象和子类的问题

时间:2010-06-29 16:49:34

标签: c# .net multithreading inheritance

所以,我有一个基类,它有一个像这样的私有锁定对象:

class A
{
    private object mLock = new object();

    public virtual void myMethod()
    {
        lock(mLock)
        {
            // CS
        }
    }
}

这个锁定对象用于大多数A的操作......因为它们需要是线程安全的。

现在,让我说我继承A就像这样:

class B : public A
{
    public override void myMethod()
    {
        lock(???)
        {
            // CS of mymethod here

            // Call base (thread safe alread)
            base.myMethod();
        }
    }
}

使B线程安全的惯例是什么? B还应该有像A这样的私有锁定对象吗?如果我需要像上面那样调用基本方法怎么办?

我想我只是好奇当基类已经是线程安全时,使子类线程安全的约定是什么。谢谢!

编辑:有些人问我的意思是“线程安全”......我将通过补充说明我试图通过互斥来实现线程安全...只有一个线程一次应该执行可能改变对象状态的代码。

5 个答案:

答案 0 :(得分:4)

您可能会暴露A:

使用的锁定
protected object SyncLock { get { return mLock; } }

如果B有自己的锁定对象,你很容易就会得到:

  • 使用不同的锁,可能导致竞争条件。 (可能没问题 - 如果在持有锁B时发生的操作与持有锁A时发生的操作正交,你可能只是侥幸逃脱。)
  • 锁以不同的顺序递归取出,导致死锁。 (同样,正交性论证适用。)

由于锁在.NET中是递归的(无论好坏),如果你的覆盖锁定了与A的实现相同的对象,可以调用base.myMethod并递归获取/释放锁。

说完所有这些之后,我热衷于使大多数类非线程安全或不可变(只有关于线程的类需要线程知识)和大多数类不需要设计用于继承IMO。

答案 1 :(得分:4)

这完全取决于。如果您的子类仅使用基类中的安全方法,并且不添加任何额外的不安全状态,那么您不必执行任何操作(首选)。如果它添加了一些与基类状态无关的额外状态,那么您可以在子类中创建一个单独的锁对象并使用它。另一方面,如果需要确保以某种“事务”方式更改基类中的新状态和状态,那么我将使基类中的锁对象受到保护并使用它。 / p>

答案 2 :(得分:4)

没有足够的信息来回答你的问题,但首先要做的事情。

仅使用lock块不会使您的代码成为线程安全的。

你在A中所做的一切都是让一次多个线程无法调用你在lock(mLock)中包含其身体的任何函数。线程安全是一个广义的术语,防止并发调用只是一个方面(并不总是你想要或需要的)。如果这就是你所需要的,那么你显然采取了正确的方法。请相信它。

其次,您需要向子类公开的内容在上面的代码中并不明显。您有三种情况:

  1. B可能会调用protected(或internal,如果它与A在同一个程序集中)A上的lock(mLock)函数未包含在{B中1}}阻止
  2. A只调用lock(mLock)块中包含的B块中的函数,并且不提供任何需要您阻止并发调用的操作
  3. A只调用lock(mLock)块中包含的B块中的函数,并提供自己的操作,要求您阻止并发调用。
  4. 这真的归结为两个无关的问题:

    1. A是否会以需要保护的方式与B互动(换句话说,尚未受保护的方式)?
    2. protected是否会公开需要保护的功能,如果是,他们是否应该使用相同的锁定对象?
    3. 如果1)为真2)是真的并且他们应该使用相同的锁定对象,那么你需要通过A属性公开你的锁定对象并在你的所有锁中使用它(我建议在A内使用它以便于阅读。)

      如果两者都不属实,那么不要担心。如果2)为真,但他们不需要使用相同的锁定对象(换句话说,对BB进行并发调用是可以接受的),那么不要担心暴露锁定对象。请记住,Alock(mLock)上受{{1}}保护的函数的任何调用都将锁定该对象,无论是否有任何外部锁定。

答案 3 :(得分:2)

“线程安全”是什么意思?如果这包括有关重入的任何想法 - 一个方法只能看到处于良好状态的对象的想法,那么一次只能运行一个方法 - 那么锁定方法可能无法达到预期效果。例如,如果您的对象以任何方式调用另一个对象(一个事件?),并且该对象重新调用原始对象,则内部调用将看到该对象处于“错误”状态。

出于这个原因,当你像这样锁定整个对象时,你必须要小心锁内部运行的代码:在锁内运行的“坏”代码可能会影响代码的正确性。

由于子类化引入了不受原始对象控制的代码,因此它常常被视为使用这样的自锁类的危险方式,因此一种流行的约定是“不要这样做。”

如果出现可能的问题,你仍然想要采用这种方法,那么使可锁定对象受保护而不是私有将允许你在同一个基类锁中包含子类操作。

答案 4 :(得分:-3)

我认为在这里做的恰当的事情是使用lock(this),而不是仅仅为了锁定而创建另一个对象的实例。这应该适用于两种情况。