我正在阅读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"); }
}
根据解释,任何线程都会阻塞第一个(最外面的)锁定,并且只有在外部锁定退出后才会解锁。
他说“当一个方法在锁中调用另一个方法时,嵌套锁定很有用”
为什么这有用?你什么时候需要这样做,它解决了什么问题?
答案 0 :(得分:11)
假设您有两种公共方法,A()
和B()
,它们都需要相同的锁定。
此外,假设A()
调用B()
由于客户端也可以直接调用B()
,因此您需要锁定这两种方法
因此,当A()
被调用时,B()
将再次锁定。
答案 1 :(得分:10)
这样做并不是很有用,因为这样做很有用。考虑如何经常使用调用其他公共方法的公共方法。如果公共方法调用了lock,并且调用它的公共方法需要锁定它所做的更广泛的范围,那么能够使用递归锁意味着你可以这样做。
在某些情况下,您可能会觉得使用两个锁定对象,但是您将一起使用它们,因此如果您犯了错误,则存在很大的死锁风险。如果你可以处理给予锁的更广泛的范围,那么在两种情况下使用相同的对象 - 并且在你将使用这两个对象的情况下递归 - 将删除那些特定的死锁。
<强>然而... 强>
这种用处是值得商榷的。
在第一个案例中,我将引用Joe Duffy:
递归通常表示同步设计过于简化,这通常会导致代码不太可靠。一些设计使用锁递归作为一种方法来避免将函数拆分为带锁的函数和假设已经采用锁的函数。这无疑会导致代码大小的减少,从而缩短写入时间,但最终导致设计更加脆弱。 将代码分解为采用非递归锁定的公共入口点以及断言锁定的内部工作程序函数始终是一个更好的主意。递归锁定调用是导致原始性能开销的冗余工作。但更糟糕的是,取决于递归会使您更难理解程序的同步行为,特别是在不变量应该保持的边界。通常我们想说锁定获取后的第一行代表对象的一个不变的“安全点”,但是一旦引入了递归,就不再能够自信地表达这个语句。这反过来使得在动态组合时更难以确保正确和可靠的行为。
(Joe在他的博客的其他地方以及his book on concurrent programming)中有更多关于该主题的说法。
第二种情况通过递归锁定条目只会发生不同类型的死锁的情况来平衡,或者将争用率推高到可能存在死锁的情况(This guy说他更喜欢它只是在你第一次递归时遇到僵局,我不同意 - 我更喜欢它只是为了抛出一个很大的异常,它带来了一个很好的堆栈跟踪我的应用程序。)
其中一个更糟糕的事情是它在错误的时间简化了:当你编写代码时,使用锁定递归比分解更多东西更简单,更深入地思考什么时候应该锁定。但是,当您调试代码时,保留锁定这一事实并不意味着让该锁定变得复杂。这是一个多么糟糕的方式 - 当我们认为我们知道我们正在做什么时,复杂的代码是在你的休息时间享受的诱惑,所以你不要在时间上放纵,当我们意识到我们搞砸了我们最希望事情变得美好而简单。
You really don't want to mix them with condition variables.
嘿,POSIX-threads only has them because of a dare!
至少lock
关键字意味着我们避免了每个Monitor.Exit()
都没有匹配Monitor.Enter()
s的可能性,这使得某些风险的可能性降低。直到您需要在该模型之外执行某些操作。
使用更新的锁定类,.NET可以帮助人们避免使用锁定递归,而不会阻止那些使用旧编码模式的人。 ReaderWriterLockSlim
有一个构造函数重载,允许您使用它递归,但默认值为LockRecursionPolicy.NoRecursion
。
通常在处理并发问题时,我们必须在更加充分的技术之间做出决定,这种技术可能会给我们带来更好的并发性,但是需要更多的关注以确保正确性与更简单的技术相比,这可能会带来更糟的并发性但是在哪里更容易确定正确性。以递归方式使用锁为我们提供了一种技术,在这种技术中,我们将保持锁更长并且并发性较差,并且不太确定正确性并且调试更难。
答案 2 :(得分:1)
如果您有一个您想要独占控制的资源,但许多方法都会对此资源起作用。方法可能无法假设它已被锁定,因此它会将其锁定在其方法中。如果它被锁定在外部方法AND内部方法中,那么它提供了类似于书中示例的情况。我无法在同一代码块中看到我想要锁定两次的时间。