18

我正在阅读 Albahari 关于线程的优秀电子书,遇到了以下场景,他提到“线程可以以嵌套(可重入)方式重复锁定同一个对象”

lock (locker)
  lock (locker)
    lock (locker)
    {
       // Do something...
    }

static readonly object _locker = new object();

static void Main()
{
  lock (_locker)
  {
     AnotherMethod();
     // We still have the lock - because locks are reentrant.
  }
}

static void AnotherMethod()
{
  lock (_locker) { Console.WriteLine ("Another method"); }
}

根据解释,任何线程都会在第一个(最外层)锁上阻塞,并且只有在外层锁退出后才会解锁。

他说“当一种方法在锁中调用另一种方法时,嵌套锁定很有用”

为什么这很有用?你什么时候需要这样做,它解决了什么问题?

4

3 回答 3

11

假设您有两个公共方法A()B(),它们都需要相同的锁。

此外,假设A()调用B()

由于客户端也可以B()直接调用,所以需要锁定这两个方法。
因此,当A()被调用时,B()会第二次取锁。

于 2012-08-17T20:59:53.307 回答
10

这样做并不是很有用,而是被允许这样做很有用。考虑一下您可能经常拥有调用其他公共方法的公共方法。如果公共方法调用锁,并且调用它的公共方法需要锁定它所做的更广泛的范围,那么能够使用递归锁意味着您可以这样做。

在某些情况下,您可能想使用两个锁对象,但您将一起使用它们,因此如果您犯了错误,就会有很大的死锁风险。如果您可以处理赋予锁的更广泛的范围,那么在两种情况下使用相同的对象 - 并在使用这两种对象的情况下递归 - 将消除那些特定的死锁。

然而...

这种有用性是值得商榷的。

在第一种情况下,我将引用Joe Duffy的话:

递归通常表示同步设计中的过度简化,这通常会导致代码可靠性降低。一些设计使用锁递归来避免将函数拆分为那些需要锁的函数和那些假设锁已经被占用的函数。诚然,这可以减少代码大小,从而缩短编写时间,但最终会导致设计更加脆弱。将代码分解到采用非递归锁的公共入口点中总是一个更好的主意,并且持有断言锁的内部工作函数。递归锁调用是导致原始性能开销的冗余工作。但更糟糕的是,依赖递归会使理解程序的同步行为变得更加困难,特别是在不变量应该保持的边界上。通常我们想说,获取锁后的第一行代表一个对象的不变“安全点”,但是一旦引入递归,就不能再自信地做出这个陈述。这反过来又使得在动态组合时确保正确和可靠的行为变得更加困难。

(Joe 在他的博客和他的关于并发编程的书中有更多关于这个主题的内容)。

第二种情况与递归锁条目只会导致不同类型的死锁发生的情况相平衡,或者将争用率推高到可能出现死锁的情况(这个人说他更喜欢它只是为了遇到死锁您第一次递归时,我不同意-我更希望它只是抛出一个大异常,使我的应用程序因良好的堆栈跟踪而崩溃)。

最糟糕的事情之一是它在错误的时间进行了简化:当您编写代码时,使用锁递归可能比将事情更多地拆分并更深入地思考什么时候应该锁定更简单。但是,当您调试代码时,离开锁并不意味着离开该锁这一事实会使事情复杂化。多么糟糕的方法 - 当我们认为我们知道我们在做什么时,复杂的代码是一种诱惑,可以在你的下班时间享受,所以你不要在上班时沉迷,当我们意识到我们搞砸了我们最希望事情变得美好而简单。

您真的不想将它们与条件变量混合。

嘿,POSIX-threads只因敢于拥有它们!

至少关键字意味着我们避免了每个s 都lock没有匹配 s 的可能性,这使得一些风险不太可能发生。直到您需要在该模型之外做一些事情。Monitor.Exit()Monitor.Enter()

使用更新的锁定类,.NET 帮助人们避免使用锁递归,而不会阻止那些使用旧编码模式的人。ReaderWriterLockSlim有一个构造函数重载,可以让你使用它递归,但默认是LockRecursionPolicy.NoRecursion.

Often in dealing with issues of concurrency we have to make a decision between a more fraught technique that could potentially give us better concurrency but which requires much more care to be sure of correctness vs a simpler technique that could potentially give worse concurrency but where it is easier to be sure of the correctness. Using locks recursively gives us a technique where we will hold locks longer and have less good concurrency, and also be less sure of correctness and have harder debugging.

于 2012-08-17T22:57:24.163 回答
1

如果您有一个想要独占控制的资源,但许多方法都对该资源起作用。一个方法可能无法假定它已被锁定,因此它将锁定它在它的方法中。如果它被锁定在外部方法和内部方法中,那么它提供了类似于书中示例的情况。我看不到我想在同一个代码块中锁定两次的时间。

于 2012-08-17T21:00:56.073 回答