你何时会使用嵌套锁定?

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

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

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

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

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

在某些情况下,您可能会觉得使用两个锁定对象,但是您将一起使用它们,因此如果您犯了错误,则存在很大的死锁风险。 如果你可以处理给予锁的更广泛的范围,那么对于这两种情况使用相同的对象 – 并且在你将使用两个对象的情况下递归 – 将删除那些特定的死锁。

然而…

这个用处是值得商榷的。

在第一个案例中,我将引用Joe Duffy的话 :

递归通常表示同步设计中的过度简化通常会导致代码不太可靠。 一些设计使用锁递归作为一种方法来避免将函数拆分为带锁的函数和假设已经采用锁的函数。 这无疑会导致代码大小的减少,从而缩短写入时间,但最终会导致更脆弱的设计。 将代码分解为采用非递归锁定的公共入口点以及断言锁定的内部工作程序函数始终是一个更好的主意。 递归锁定调用是导致原始性能开销的冗余工作。 但更糟糕的是,取决于递归会使您更难理解程序的同步行为,特别是在不变量应该保持的边界。 通常我们想说锁定获取后的第一行代表对象的一个​​不变的“安全点”,但是一旦引入了递归,就不再能够自信地表达这种语句。 这反过来使得在动态组合时确保正确和可靠的行为更加困难。

(Joe在他博客的其他地方以及他关于并发编程的书中有更多的话要说。

第二种情况是通过递归锁定进入只会使不同类型的死锁发生的情况来平衡,或者推高争用率以至于可能存在死锁( 这个人说他更喜欢它只是为了陷入僵局你第一次递归时,我不同意 – 我更喜欢它只是抛出一个很大的exception,它带来了一个很好的堆栈跟踪我的应用程序)。

更糟糕的是,它是在错误的时间简化:当你编写代码时,使用锁定递归比分解更多东西更简单,并且更深入地思考什么时候应该锁定。 但是,当您调试代码时,保留锁定这一事实并不意味着使该锁定变得复杂。 这是一个多么糟糕的方式 – 当我们认为我们知道我们正在做什么时,复杂的代码是在你的rest时间享受的诱惑,所以你不要在时间上放纵,当我们意识到我们搞砸了我们最希望事情变得美好而简单。

你真的不想将它们与条件变量混合在一起。

嘿,POSIX线程只有它们才有胆量!

至少lock关键字意味着我们避免了每个Monitor.Enter() s都没有匹配Monitor.Exit()的可能性,这使得一些风险的可能性降低。 直到您需要在该模型之外执行某些操作。

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

通常在处理并发问题时,我们必须在更加充分的技术之间做出决定,这种技术可能会给我们带来更好的并发性,但需要更加谨慎,以确保正确性与更简单的技术相比,这种技术可能会带来更糟的并发性,但它在哪里更容易确定正确性。 以递归方式使用锁为我们提供了一种技术,在这种技术中,我们将保持锁更长并且并发性较差,并且不太确定正确性并且调试更难。

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

而且,假设A()调用B()

由于客户端也可以直接调用B() ,因此需要锁定这两种方法。
因此,当调用A()时, B()将再次进行锁定。

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