2008-10-09 159 views
152

POSIX允许互斥锁递归。这意味着同一个线程可以锁定相同的互斥锁两次,而不会发生死锁。当然,它也需要解锁两次,否则没有其他线程可以获得互斥锁。并非所有支持pthread的系统都支持递归互斥,但是如果他们想要成为POSIX conform, they have to递归锁(Mutex)与非递归锁(Mutex)

其他API(更高级别的API)通常也提供互斥锁,通常称为Locks。某些系统/语言(例如Cocoa Objective-C)提供递归和非递归互斥。一些语言也只提供一种或另一种。例如。在Java中,互斥量总是递归的(同一个线程可能会在同一个对象上两次“同步”)。根据他们提供的其他线程功能,没有递归互斥可能没有问题,因为它们可以很容易地自己编写(我已经基于更简单的互斥/条件操作自己实现了递归互斥锁)。

我真的不明白什么是非递归互斥体?为什么我想要一个线程死锁,如果它锁定相同的互斥锁两次?即使是能够避免这种情况的高级语言(例如,如果测试会发生死锁并抛出异常,通常也不会这样做)。他们会让线程死锁。

这是唯一的情况下,我不小心锁定它两次,只解锁一次,并在递归互斥体的情况下,它会很难找到问题,所以相反,我立即死锁,看看哪里不正确锁出现?但是我不能在解锁时返回一个锁定计数器,并且在我确定释放了最后一个锁并且计数器不为零的情况下,我可以抛出异常或记录问题吗?还是有没有其他更有用的非递归互斥体的用例,我没有看到?或者它可能只是性能,因为非递归互斥体可能比递归互斥体稍快一些?但是,我测试了这个,差别并不大。

回答

123

递归与非递归互斥之间的区别与所有权有关。在递归互斥体的情况下,内核必须跟踪第一次实际获得互斥体的线程,以便它可以检测到递归与应该阻塞的不同线程之间的差异。正如另一个答案所指出的那样,存在这样的额外开销的问题,无论是在内存方面还是在存储这个上下文方面,还有维护它所需的周期。

但是,这里也有其他的考虑。

因为递归互斥体拥有所有权,抓取互斥体的线程必须与释放互斥体的线程相同。在非递归互斥体的情况下,不存在所有权,任何线程通常都可以释放互斥体,而不管哪个线程原先接受互斥体。在很多情况下,这种“互斥体”实际上更像是信号量动作,在这种情况下,您不一定要将互斥体用作排除设备,而是将其用作两个或更多线程之间的同步或信号设备。

在互斥体中拥有所有权感的另一个属性是支持优先级继承的能力。因为内核可以跟踪拥有该互斥体的线程以及所有阻塞程序的身份,所以在优先级线程系统中,可以将当前拥有该互斥体的线程的优先级升级为最高优先级线程的优先级目前阻止互斥体。这种继承可以防止在这种情况下可能发生的优先倒置问题。 (请注意,并非所有系统都支持此类互斥锁上的优先级继承,但它是通过所有权概念实现的另一个功能)。

如果您参考经典的VxWorks RTOS内核,它们定义了三种机制:

  • 互斥 - 支持递归,和可选的优先级继承
  • 二进制信号 - 没有递归,没有继承,简单排除,接受者和送礼者不必是相同的线程,广播发布可用
  • 计数信号量 - 没有递归或继承,作为来自任何期望的初始计数的连贯资源计数器,线程仅阻止针对资源的净计数为零。

同样,这种情况在平台上有所不同 - 特别是他们称之为的东西,但这应该是概念和各种机制的代表。

+4

你对非递归互斥的解释听起来更像是一个信号量。互斥体(无论是递归的还是非递归的)都有一个所有权的概念。 – 2011-01-07 22:14:34

+0

@JayD当人们争论这些事情时,这是非常混乱的......所以谁来定义这些东西的实体? – Pacerier 2011-12-08 16:09:52

+8

@Pacerier相关标准。这个答案是例如posix(pthreads)错误,在锁定它的线程以外的其他线程中解锁正常的互斥锁是未定义的行为,而在错误检查或递归互斥体中执行同样的操作则会导致可预测的错误代码。其他系统和标准可能会有很大不同。 – nos 2012-08-06 21:26:39

103

答案是不是效率。不可重入的互斥体导致更好的代码。

示例:A :: foo()获取该锁。然后它调用B :: bar()。当你写它时,这工作得很好。但是稍后有人更改B :: bar()来调用A :: baz(),该函数也获取了锁。

那么,如果你没有递归互斥,这个死锁。如果你有他们,它会运行,但它可能会中断。在调用bar()之前,A :: foo()可能使对象处于不一致的状态,假设baz()无法运行,因为它也获取了互斥锁。但它可能不应该运行!写A :: foo()的人认为没有人可以同时调用A :: baz() - 这是这两种方法获得锁的全部原因。

使用互斥锁的正确思维模型:互斥锁保护不变量。当互斥体被保持时,不变量可能会改变,但在释放互斥体之前,不变量会重新建立。再入式锁定非常危险,因为第二次获取锁定时,不能确定不变是否为真。

如果您对重入锁很满意,那只是因为您之前没有必须调试过这样的问题。顺便说一下,Java在java.util.concurrent.locks中有近来的非重入锁。

10

使用 互斥正确的心智模型:互斥锁保护的 不变。

为什么你确定这是真正正确的使用互斥体的心智模型? 我认为正确的模式是保护数据,但不是不变量。

即使在单线程应用程序中也存在保护不变量的问题,并且与多线程和互斥体没有任何共同之处。另外,如果你需要保护不变量,你仍然可以使用永远不会递归的二进制信号量。

78

As written by Dave Butenhof himself

“最大的所有递归互斥体的大问题是,他们 鼓励你完全失去你的锁定方案和 范围的轨道。这是致命的。邪恶。这是“线程食客”。您在最短的时间内持有锁锁 。期。总是。如果你打电话给 ,因为你不知道是否持有锁,或者因为你不知道被调用者是否需要互斥锁,所以你锁定的时间太长,因此你需要锁 。你正在瞄准你的应用中的霰弹枪,并拉动扳机。你可能开始使用线程来获得并发性;但你刚刚预防并发。“

2

递归互斥是有用的一个主要原因是在同一线程多次访问方法的情况下。例如,假如互斥锁正在保护银行A/C到如果还有一个费用也与撤回有关,那么必须使用相同的互斥体

0

递归互斥体的唯一良好用例是当一个对象包含多个方法时当任何方法修改如果该方法使用其他方法(即:addNewArray()调用addNewPoint(),并使用rec进行最终确定heckBounds()),但其中的任何函数本身都需要锁定互斥锁,然后递归互斥锁才是双赢的。

对于任何其他情况(解决不好的代码,甚至在不同的对象中使用它)显然是错误的!