Mec*_*cki 176 multithreading mutex deadlock locking recursive-mutex
POSIX允许互斥锁递归.这意味着同一个线程可以锁定相同的互斥锁两次并且不会死锁.当然它还需要解锁两次,否则没有其他线程可以获得互斥锁.并非所有支持pthread的系统都支持递归互斥锁,但如果它们想要符合POSIX,则必须使用.
其他API(更高级别的API)通常也提供互斥锁,通常称为锁定.一些系统/语言(例如Cocoa Objective-C)提供递归和非递归互斥体.有些语言也只提供一种或另一种语言.例如,在Java中,互斥锁总是递归的(同一个线程可能在同一个对象上"同步"两次).根据它们提供的其他线程功能,没有递归互斥体可能没有问题,因为它们可以很容易地自己编写(我已经在更简单的互斥/条件操作的基础上自己实现了递归互斥锁).
我真的不明白:什么是非递归互斥量有用?如果它锁定相同的互斥锁两次,为什么我想要一个线程死锁?即使是可以避免这种情况的高级语言(例如测试它是否会死锁并抛出异常)通常也不会这样做.他们会让线程陷入僵局.
这只适用于我意外锁定它两次并且只解锁一次的情况,并且在递归互斥锁的情况下,它会更难找到问题,所以相反我立即死锁以查看错误锁定出现在哪里?但是我不能在解锁时返回一个锁定计数器并且在某种情况下,我确定我释放了最后一个锁并且计数器不为零,我可以抛出异常或记录问题吗?或者是否有其他更有用的非递归互斥体用例我看不到?或者它可能只是性能,因为非递归互斥体可能比递归互斥体略快?但是,我对此进行了测试,差异确实不大.
Tal*_*eff 147
递归和非递归互斥体之间的区别与所有权有关.在递归互斥锁的情况下,内核必须跟踪第一次实际获得互斥锁的线程,以便它可以检测递归与应该阻塞的不同线程之间的差异.正如另一个答案所指出的那样,在存储该上下文的存储器方面以及维护它所需的周期方面存在额外开销的问题.
但是,这里还有其他考虑因素.
因为递归互斥锁具有所有权感,所以抓取互斥锁的线程必须与释放互斥锁的线程相同.在非递归互斥锁的情况下,没有所有权感,任何线程通常都可以释放互斥锁,无论哪个线程最初使用互斥锁.在许多情况下,这种类型的"互斥体"实际上更像是一种信号量动作,您不必将互斥体用作排除设备,而是将其用作两个或多个线程之间的同步或信号设备.
另一个在互斥体中具有所有权感的属性是支持优先级继承的能力.因为内核可以跟踪拥有互斥锁的线程以及所有阻塞程序的标识,所以在优先级线程系统中,可以将当前拥有互斥锁的线程的优先级升级到最高优先级线程的优先级目前阻止互斥锁.这种继承防止了在这种情况下可能发生的优先级倒置问题.(请注意,并非所有系统都支持此类互斥锁上的优先级继承,但它是通过所有权概念实现的另一个功能).
如果您参考经典的VxWorks RTOS内核,它们定义了三种机制:
同样,这在某种程度上因平台而异 - 特别是他们称之为的东西,但这应该代表了概念和各种机制.
Jon*_*han 119
答案不是效率.不可重入的互斥体可以带来更好的代码.
示例:A :: foo()获取锁定.然后调用B :: bar().当你写它时,这很好用.但有时候有人改变B :: bar()来调用A :: baz(),它也获得了锁.
好吧,如果你没有递归的互斥锁,那么这种死锁.如果你有它们,它会运行,但它可能会中断.在调用bar()之前,A :: foo()可能使对象处于不一致状态,假设baz()无法运行,因为它也获取了互斥锁.但它可能不应该运行!编写A :: foo()的人假设没有人可以同时调用A :: baz() - 这就是这两个方法都获得锁定的全部原因.
使用互斥锁的正确心理模型:互斥锁保护不变量.保持互斥锁时,不变量可能会发生变化,但在释放互斥锁之前,会重新建立不变量.重入锁定是危险的,因为第二次获得锁定时,您无法确定不变量是否为真.
如果您对可重入锁定感到满意,那只是因为您之前没有必要调试这样的问题.顺便说一下,Java现在在java.util.concurrent.locks中具有非重入锁.
Chr*_*and 88
"递归互斥体的所有重大问题中最大的问题是它们会鼓励你完全忘记你的锁定方案和范围.这是致命的.邪恶.它是"线程吞噬者".你在最短的时间内持有锁.期间.永远.如果你只是因为你不知道它被锁定而持有锁定的东西,或者因为你不知道被调用者是否需要互斥锁,那么你持有的时间太长了.你是将霰弹枪瞄准你的应用程序并拉动扳机.你可能开始使用线程来获得并发性;但你只是防止了并发性."
小智 14
使用互斥锁的正确心理模型:互斥锁保护不变量.
为什么你确定这是使用互斥锁的真正正确的心理模型?我认为正确的模型是保护数据而不是不变量.
即使在单线程应用程序中也存在保护不变量的问题,并且多线程和互斥体没有任何共同之处.
此外,如果您需要保护不变量,您仍然可以使用二进制信号量,它永远不会递归.
递归互斥锁唯一好的用例是当一个对象包含多个方法时。当任何方法修改对象的内容时,因此必须在状态再次一致之前锁定对象。
如果这些方法使用其他方法(即:addNewArray() 调用 addNewPoint(),并使用 recheckBounds() 完成),但这些函数中的任何一个本身都需要锁定互斥锁,那么递归互斥锁是双赢的。
对于任何其他情况(解决糟糕的编码,甚至在不同的对象中使用它)显然是错误的!
| 归档时间: |
|
| 查看次数: |
111406 次 |
| 最近记录: |