递归锁(Mutex)与非

POSIX允许互斥体递归。 这意味着同一个线程可以锁定相同的互斥锁两次,而不会造成死锁。 当然,它也需要解锁两次,否则没有其他线程可以获得互斥锁。 并不是所有支持pthread的系统都支持递归互斥,但如果他们想要符合POSIX,他们必须这样做。

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

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

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


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

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

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

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

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

  • 互斥量 - 支持递归,并可选择优先级继承
  • 二进制信号量 - 无递归,无继承,简单排除,接受者和授予者不必是相同的线程,广播发布可用
  • 计数信号量 - 无递归或继承,作为任何期望初始计数的连贯资源计数器,线程仅阻止对资源的净计数为零。
  • 再次,平台会有所不同 - 特别是他们称之为什么的东西,但这应该是概念和各种机制的代表。


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

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

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

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

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


    正如Dave Butenhof本人所写:

    “递归互斥体中最大的问题是它们会促使你完全失去对锁定方案和范围的追踪,这是致命的,邪恶的,它是”线程吞噬者“,你在最短的时间内持有锁。总是,如果你只是因为你不知道它被锁定而调用某个锁,或者因为你不知道被调用者是否需要该互斥锁,那么你就持有它太久了。在你的应用程序中瞄准霰弹枪并拉动触发器,你大概会开始使用线程来获得并发性,但是你只是预防了并发。“

    链接地址: http://www.djcxy.com/p/84839.html

    上一篇: Recursive Lock (Mutex) vs Non

    下一篇: How does multilevel feedback queue scheduling works?