recursion锁(Mutex)与非recursion锁(Mutex)

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

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

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

这是唯一的情况下,我不小心locking了两次,只有一次解锁,并在recursion互斥的情况下,这将是很难find问题,所以相反,我立即死锁,看看哪里出现不正确的锁? 但是我不能在解锁时返回一个locking计数器,在这种情况下,我确信我释放了最后一个锁,而计数器不是零,我可以抛出exception或logging问题? 还是有没有其他更有用的非recursion互斥体的使用情况,我没有看到? 或者也许只是performance,因为非recursion互斥可以比recursion互斥略快一点? 但是,我testing了这个,差别并不大。

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

不过 ,这里也有其他的考虑。

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

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

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

  • 互斥锁 – 支持recursion,并可select优先级inheritance
  • 二进制信号量 – 没有recursion,没有inheritance,简单的排除,接受者和授予者不必是相同的线程,广播发布可用
  • 计数信号量 – 无recursion或inheritance,作为任何期望的初始计数的连贯资源计数器,线程仅阻止对资源的净计数为零。

同样,平台也会有所不同,特别是他们所说的这些东西,但是这应该代表了概念和各种机制。

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

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

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

使用互斥锁的正确思维模式:互斥锁保护一个不variables。 当互斥体被保持时,不variables可能会改变,但在释放互斥体之前,不variables会被重新build立。 可重入锁是危险的,因为第二次获得锁时,不能确定不变是真的。

如果你对重入锁很满意,那只是因为你以前没有必须debugging过这样的问题。 现在Java在java.util.concurrent.locks中有非重入锁。

正如Dave Butenhof本人所写 :

“recursion互斥体中最大的问题是,它们会促使你完全失去lockingscheme和范围,这是致命的,邪恶的,它是”线程吞噬者“,你可以在最短的时间内保持locking状态。总是,如果你只是因为你不知道被占用的锁而打电话给你的东西,或者你不知道被调用者是否需要这个互斥锁,那么你就持有它太久了。在你的应用程序中瞄准一个霰弹枪并拉动触发器,你可能开始使用线程来获得并发性,但是你只是预防了并发。

使用互斥锁的正确思维模式:互斥锁保护一个不variables。

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

即使在单线程应用程序中也存在保护不variables的问题,并且与multithreading和互斥体没有任何共同之处。

而且,如果你需要保护不variables,你仍然可以使用永远不会recursion的二进制信号量。

recursion互斥是有用的一个主要原因是在同一个线程多次访问方法的情况下。 例如,如果互斥锁正在保护银行账户取消,那么如果还有与该取消相关的费用,则必须使用相同的互斥锁。

recursion互斥体的唯一好用例是一个对象包含多个方法。 当任何方法修改对象的内容时,因此必须在状态再次一致之前locking对象。

如果方法使用其他方法(即:addNewArray()调用addNewPoint(),并使用recheckBounds()函数结束),但是这些函数中的任何一个都需要locking互斥锁,那么recursion互斥锁就是双赢的。

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