【问题标题】:When would you ever use nested locking?你什么时候会使用嵌套锁定?
【发布时间】:2012-08-14 07:59:22
【问题描述】:

我在阅读 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"); }
}

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

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

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

【问题讨论】:

标签: c# .net multithreading


【解决方案1】:

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

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

由于客户端也可以直接调用B(),所以这两个方法都需要加锁。
因此,当调用A() 时,B() 将再次获得锁。

【讨论】:

  • 我要补充一点,重入锁很有用,因为非重入锁在锁定两次时往往会死锁。如果您必须检查您是否已经拥有锁,那么您的代码的重点将偏离它试图实现的目标,您将在测试中包含另一个条件,而且您可能必须有一个lock 检查锁是否跨线程。
  • @tinman 正是为什么将它们添加到 POSIX 线程的人称它们为拐杖;应该清楚你是否有锁,如果没有,那就是要修复的缺陷。
【解决方案2】:

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

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

但是...

这种有用性值得商榷。

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

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

(Joe 在他的博客和his book on concurrent programming 的其他地方有更多关于这个话题的内容。

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

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

You really don't want to mix them with condition variables.

嘿,POSIX 线程 only has them because of a dare!

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

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

通常在处理并发问题时,我们必须在一个更令人担忧的技术之间做出决定,这种技术可能会给我们带来更好的并发性,但需要更加小心以确保正确性,而更简单的技术可能会带来更差的并发性,但是更容易确定正确性的地方。递归地使用锁为我们提供了一种技术,我们将持有锁的时间更长,并发性较差,而且对正确性的把握也较小,调试也更困难。

【讨论】:

  • 优秀且内容丰富的文章。让我们成为朋友吧。
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2021-11-29
    • 2017-12-10
    • 2010-11-28
    • 2010-09-19
    • 2012-04-23
    • 2018-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多