POSIX允许互斥锁是递归的。这意味着同一线程可以锁定同一互斥锁两次,而不会死锁。当然,它还需要解锁两次,否则其他线程都无法获得互斥锁。并不是所有支持pthread的系统都支持递归互斥锁,但如果它们想成为POSIX conform, they have to的话。
其他API(更高级的API)通常也提供互斥锁,通常称为锁。一些系统/语言(例如Cocoa Objective-C)同时提供递归和非递归互斥锁。有些语言也只提供一种或另一种。例如,在Java中,互斥锁总是递归的(同一个线程可以在同一个对象上“同步”两次)。根据它们提供的其他线程功能,没有递归互斥锁可能没有问题,因为它们可以很容易地自己编写(我已经在更简单的互斥锁/条件操作的基础上自己实现了递归互斥锁)。
我不太明白的是:非递归互斥锁有什么好处?为什么我要有一个线程死锁,如果它锁定同一互斥锁两次?即使是可以避免这种情况的高级语言(例如,测试这是否会死锁,如果会死锁则抛出异常)通常也不会这样做。他们会让线程死锁。
这是否仅适用于意外锁定两次而只解锁一次的情况,如果是递归互斥,则更难找到问题,因此我会立即让它死锁,以查看错误锁出现在哪里?但我不能对解锁时返回的锁计数器做同样的事情吗?在我确定释放了最后一个锁并且计数器不为零的情况下,我可以抛出异常或记录问题。或者,有没有其他更有用的非递归互斥锁的用例?或者它可能只是性能,因为非递归互斥可以比递归互斥稍微快一些?然而,我测试了这一点,差异真的不是那么大。
发布于 2008-10-10 01:09:44
递归和非递归互斥之间的区别与所有权有关。在递归互斥锁的情况下,内核必须跟踪第一次实际获得互斥锁的线程,以便它可以检测递归与应该阻塞的不同线程之间的差异。正如另一个答案所指出的,在存储上下文的内存和维护上下文所需的周期方面,都存在这样一个额外开销的问题。
然而,这里也有其他的考虑因素。
因为递归互斥锁具有所有权意识,所以获取互斥锁的线程必须与释放互斥锁的线程相同。在非递归互斥锁的情况下,没有所有权意识,并且任何线程通常都可以释放互斥锁,而不管最初是哪个线程获得该互斥锁。在许多情况下,这种类型的“互斥锁”实际上更像是一种信号量操作,在这种情况下,您不必将互斥锁用作排除设备,而是将其用作两个或多个线程之间的同步或信号设备。
在互斥锁中具有所有权意识的另一个特性是支持优先级继承的能力。因为内核可以跟踪拥有互斥锁的线程以及所有阻塞程序的标识,所以在优先级线程系统中,可以将当前拥有互斥锁的线程的优先级提升到当前阻塞互斥锁的最高优先级线程的优先级。这种继承可以防止在这种情况下可能发生的优先级反转问题。(请注意,并非所有系统都支持此类互斥锁上的优先级继承,但这是通过所有权概念实现的另一个特性)。
如果你参考经典VxWorks实时操作系统内核,它们定义了三种机制:
同样,这在不同的平台上有所不同--特别是他们所说的这些东西,但这应该是概念和各种机制的代表。
发布于 2008-11-16 07:44:44
答案不是效率。不可重入的互斥锁会产生更好的代码。
示例: A::foo()获取锁。然后它调用B::bar()。当你写它的时候,它运行得很好。但过了一段时间后,有人将B::bar()更改为调用A::baz(),A::baz()也获得锁。
好吧,如果你没有递归互斥锁,这个死锁。如果你有它们,它会运行,但它可能会崩溃。在调用bar()之前,A::foo()可能使对象处于不一致的状态,假设baz()无法运行,因为它还获取了互斥锁。但是它可能不应该运行!编写A::foo()的人假设没有人可以同时调用A::baz() -这就是这两个方法都获得锁的全部原因。
使用互斥锁的正确心理模型:互斥锁保护不变量。当互斥锁保持时,不变量可能会改变,但在释放互斥锁之前,不变量会重新建立。可重入锁是危险的,因为当您第二次获取锁时,您不能再确定不变量是否为真。
如果您对可重入锁感到满意,那只是因为您以前没有调试过这样的问题。顺便说一句,现在Java在java.util.concurrent.locks中有不可重入的锁。
发布于 2009-08-07 14:20:38
As written by Dave Butenhof himself
“递归互斥最大的问题是,它们会鼓励你完全忘记你的锁定方案和作用域。这是致命的。邪恶。这是“食线者”。你持有锁的时间绝对是最短的。句号。一直都是。如果你仅仅因为你不知道锁被持有,或者因为你不知道被调用者是否需要互斥锁而调用锁,那么你持有锁的时间太长了。您正在将猎枪对准您的应用程序并扣动扳机。您可能开始使用线程来获得并发性;但您刚刚阻止了并发性。“
https://stackoverflow.com/questions/187761
复制相似问题