https://en.cppreference.com/w/cpp/thread/lock_tag
void transfer(bank_account &from, bank_account &to, int amount)
{
// lock both mutexes without deadlock
std::lock(from.m, to.m);
// make sure both already-locked mutexes are unlocked at the end of scope
std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
// equivalent approach:
// std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
// std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
// std::lock(lock1, lock2);
from.balance -= amount;
to.balance += amount;
}
一次锁定两个互斥锁会得到什么?
他们通过在这里延迟锁定获得了什么?
请解释他们做出该决定的原因。
答案 0 :(得分:7)
如果我在不锁定银行帐户的情况下对其进行修改,则其他人可能会尝试同时对其进行修改。这是一场比赛,其结果将是不确定的行为(通常是损失或神奇创造的金钱)。
在转帐时,我正在修改2个银行帐户。因此它们都需要被锁定。
问题是,当锁定多个东西时,每个储物柜必须以相同的顺序锁定和解锁,否则会出现死锁。
当是银行帐户时,没有自然的锁顺序。成千上万的线程可能会向各个方向转移资金。
因此,我们需要一种可以解决此问题的方法来锁定多个互斥体的方法-这是std::lock
std::lock
仅锁定互斥锁-它不能保证在退出当前代码块时解锁。
std::lock_guard<>
在销毁时解锁它所指的互斥锁(请参阅RAII)。这使代码在所有情况下均能正常运行-即使存在可能导致当前代码块提前退出而没有代码流过诸如to.m.unlock()
这里有一个很好的解释(带有示例):https://wiki.sei.cmu.edu/confluence/display/cplusplus/CON53-CPP.+Avoid+deadlock+by+locking+in+a+predefined+order
答案 1 :(得分:1)
一次锁定两个互斥锁会得到什么?
Richard已经很好地解释了,只是更加明确了一点:我们以这种方式避免死锁({std::lock
被实现为不会发生死锁)。
通过这里的延迟锁定,他们获得了什么?
推迟锁定将导致无法立即获取它。这很重要,因为如果他们 did 这样,他们将这样做而没有任何防止死锁的保护(随后的std::lock
随后会实现)。
关于避免死锁(请参阅std::lock):
使用避免死锁算法来避免死锁来锁定给定的可锁定对象lock1,lock2,...,lockn。
对象由一系列未指定的锁定,try_lock和unlock调用锁定。 [...]
侧面说明:避免死锁的另一种更简单的算法是始终使用e锁定银行帐户。 G。请先输入较低的帐号(AN)。如果一个线程正在等待较高AN的锁,则持有该线程的另一个线程要么已经获得了两个锁,要么正在等待第二个锁–它不能是第一个线程,因为它必须具有更高的AN
对于任意数量的线程来说,这不会有太大变化,任何持有较低锁的线程都在等待较高锁(如果也持有)。如果在A等待B持有的第二把锁的情况下绘制从A到B的有向图,则会得到一个(多)树结构,但永远不会有圆形子结构(这表示死了)锁定)。
答案 2 :(得分:0)
from
和to
是2
个帐户,可以分别在应用程序中的任何地方使用。
通过为每个帐户设置互斥量,您可以确保没人在进行转移时使用from
或to
帐户。
lock_guard
将在从功能中退出时释放互斥锁。
答案 3 :(得分:0)
银行帐户数据结构的每个帐户都有一个锁。
将资金从一个帐户转移到另一个帐户时,我们需要锁定两个帐户(因为我们要从一个帐户中删除资金并将其添加到另一个帐户中)。我们希望此操作不会死锁,因此请使用std::lock
一次锁定两者,因为这样做可以确保没有死锁。
完成交易后,我们需要确保释放锁。该代码使用RAII来完成。通过adopt_lock
标签,我们使对象采用已经锁定的互斥锁(当lock1
超出范围时将释放该互斥锁)。
使用defer_lock
标签,我们为当前未锁定的互斥锁创建一个unique_lock
,目的是稍后对其进行锁定。同样,当unique_lock
超出范围时,它将被解锁。