为什么以不同的顺序解锁两个锁定的银行账户会导致死锁?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么以不同的顺序解锁两个锁定的银行账户会导致死锁?相关的知识,希望对你有一定的参考价值。

https://stackoverflow.com/a/51890291/462608

如果我修改银行帐户而不锁定它,其他人可能会尝试同时修改它。这是一场比赛,结果将是未定义的行为(通常是丢失或神奇创造的钱)。

在转账时,我正在修改2个银行账户。所以他们都需要被锁定。

问题是当锁定多个东西时,每个锁定器必须以相同的顺序锁定和解锁,否则我们会遇到死锁。

示例:从帐户A中提取并存入帐户B.

所以,我先锁定A然后再锁定B.然后,如果我解锁B然后A,为什么会导致死锁?

请解释。

答案

延伸到Richard Hodgesanswer

通过一次锁定两个互斥锁可以获得什么?

理查德已经很好地解释了,只是更明确一点:我们以这种方式避免死锁(实施std::lock使得不会发生死锁)。

他们通过延迟锁获得了什么?

推迟锁定会导致无法立即获取锁定。这一点非常重要,因为如果他们这样做,他们就会在没有任何防止死锁的情况下做到这一点(后来的std::lock就会实现)。

关于避免死锁(参见qazxsw poi):

使用死锁避免算法锁定给定的Lockable对象lock1,lock2,...,lockn以避免死锁。

这些对象被一系列未指定的锁定,try_lock和unlock调用锁定。 [...]

旁注:另一个更简单的避免死锁的算法总是用e锁定银行账户。 G。首先是较低的帐号(AN)。如果一个线程正在等待更高AN的锁定,那么持有它的另一个线程已经获得了两个锁定或正在等待第二个 - 这不能是第一个线程之一,因为它必须具有更高的AN 。

对于任意数量的线程,这没有太大变化,任何持有较低锁的线程都在等待更高的线程,如果保持也是如此。如果你绘制一个边缘从A到B的有向图,如果A正在等待B持有的第二个锁,你将得到一个(多)树结构,但你不会有圆形子结构(这表明死了锁)。

另一答案

如果我修改银行帐户而不锁定它,其他人可能会尝试同时修改它。这是一场比赛,结果将是未定义的行为(通常是丢失或神奇创造的钱)。

在转账时,我正在修改2个银行账户。所以他们都需要被锁定。

问题是当锁定多个东西时,每个锁定器必须以相同的顺序锁定和解锁,否则我们会遇到死锁。

当它是银行账户时,没有自然的锁定顺序。成千上万的线程可以向各个方向转移资金。

所以我们需要一种方法来锁定多个互斥锁,以此方式解决这个问题 - 这就是

std::lock只是锁定互斥锁 - 它不保证在退出当前代码块时解锁。

std::lock解锁了它在破坏时提到的互斥锁(参见RAII)。这使代码在所有情况下都能正常运行 - 即使存在可能导致提前退出当前代码块的异常,而代码不会流过语句,例如std::lock_guard<>

这里有一个很好的解释(带例子):​​to.m.unlock()

另一答案

银行帐户数据结构具有每个帐户的锁定。

在将钱从一个账户转移到另一个账户时,我们需要锁定两个账户(因为我们从一个账户中取钱并将其添加到另一个账户)。我们希望这个操作不会死锁,所以使用同时锁定两个,因为这样做可以确保没有死锁。

在我们完成交易后,我们需要确保释放锁定。此代码使用RAII执行此操作。使用std::lock标记,我们使对象采用已锁定的互斥锁(当adopt_lock超出范围时将释放该互斥锁)。使用lock1标签,我们为当前解锁的互斥锁创建一个defer_lock,以便稍后锁定它。再次,当unique_lock超出范围时,它将被解锁。

另一答案

fromto账户,可以在申请中的任何地方单独使用。

通过为每个帐户使用互斥锁,您确保在执行转移时没有人使用2from帐户。

to将在退出函数时释放互斥锁。

另一答案

因为您可以满足以下顺序:

  • 线程1锁定A.
  • 线程2锁B.
  • 线程1尝试锁定B和块。
  • 线程2尝试锁定A和块。

两个线程都在等待,所以你有一个死锁。

为避免这种情况,您始终必须以相同的顺序获取锁。解锁订单无关紧要。

以上是关于为什么以不同的顺序解锁两个锁定的银行账户会导致死锁?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 间隙锁

避免死锁的银行家算法

防止代码死锁的锁定策略和技术

db2数据库里面的一张表被锁定,怎么解锁

死锁产生的原因及避免死锁的方法

由于不当的执行顺序导致的死锁