为什么我要在一个函数中锁定两个互斥量——延迟锁定也是如此?
Why would I want to lock two mutexes in one function - that too with deferred lock?
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;
}
他们通过同时锁定两个互斥锁获得了什么?
他们在这里通过延迟锁定获得了什么?
请解释他们做出该决定的原因。
from
和 to
是 2
帐户,可以在应用程序的任何地方单独使用。
通过为每个帐户设置互斥锁,您可以确保在您进行转账时没有人使用from
或to
个帐户。
lock_guard
将在 退出 函数时释放互斥量。
如果我在不锁定的情况下修改银行账户,其他人可能会同时尝试修改它。这是一场比赛,结果将是不确定的行为(通常是丢失或神奇创造的钱)。
在转账的时候,我正在修改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
银行账户数据结构对每个账户都有一把锁。
将资金从一个账户转移到另一个账户时,我们需要锁定两个账户(因为我们要从一个账户中取出资金并添加到另一个账户中)。我们希望此操作不会死锁,因此使用 std::lock
同时锁定两者,因为这样做可确保不会出现死锁。
完成事务后,我们需要确保释放锁。此代码使用 RAII 执行此操作。使用 adopt_lock
标记,我们使对象采用一个已经锁定的互斥量(当 lock1
超出范围时将被释放)。
使用 defer_lock
标记,我们为当前解锁的互斥量创建了一个 unique_lock
,目的是稍后锁定它。同样,当 unique_lock
超出范围时它将被解锁。
延期 Richard Hodges's
What do they gain by locking two mutexes at once?
Richard 已经很好地解释了,只是更明确了一点:我们通过这种方式避免了死锁(std::lock
的实现使得死锁不会发生)。
What have they gained by deferred lock here?
延迟锁定会导致无法立即获取它。这很重要,因为如果他们 这样做了 ,他们就会在没有任何防止死锁的保护的情况下这样做(随后的 std::lock
然后实现)。
关于避免死锁(参见std::lock):
Locks the given Lockable objects lock1, lock2, ..., lockn using a deadlock avoidance algorithm to avoid deadlock.
The objects are locked by an unspecified series of calls to lock, try_lock, and unlock. [...]
旁注:另一个更简单的避免死锁的算法总是用 e 锁定银行账户。 G。首先是较低的帐号 (AN)。如果一个线程正在等待更高 AN 的锁,那么持有它的另一个线程要么已经获得了两个锁,要么正在等待第二个——它不能是第一个线程之一,因为它必须有更高的 AN .
对于任意数量的线程,这并没有太大变化,任何持有较低锁的线程都在等待较高的锁,如果也持有的话。如果你绘制一个有向图,边从 A 到 B,如果 A 正在等待 B 持有的第二个锁,你将得到一个(多)树结构,但你永远不会有圆形子结构(这表明死了锁)。
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;
}
他们通过同时锁定两个互斥锁获得了什么?
他们在这里通过延迟锁定获得了什么?
请解释他们做出该决定的原因。
from
和 to
是 2
帐户,可以在应用程序的任何地方单独使用。
通过为每个帐户设置互斥锁,您可以确保在您进行转账时没有人使用from
或to
个帐户。
lock_guard
将在 退出 函数时释放互斥量。
如果我在不锁定的情况下修改银行账户,其他人可能会同时尝试修改它。这是一场比赛,结果将是不确定的行为(通常是丢失或神奇创造的钱)。
在转账的时候,我正在修改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
银行账户数据结构对每个账户都有一把锁。
将资金从一个账户转移到另一个账户时,我们需要锁定两个账户(因为我们要从一个账户中取出资金并添加到另一个账户中)。我们希望此操作不会死锁,因此使用 std::lock
同时锁定两者,因为这样做可确保不会出现死锁。
完成事务后,我们需要确保释放锁。此代码使用 RAII 执行此操作。使用 adopt_lock
标记,我们使对象采用一个已经锁定的互斥量(当 lock1
超出范围时将被释放)。
使用 defer_lock
标记,我们为当前解锁的互斥量创建了一个 unique_lock
,目的是稍后锁定它。同样,当 unique_lock
超出范围时它将被解锁。
延期 Richard Hodges's
What do they gain by locking two mutexes at once?
Richard 已经很好地解释了,只是更明确了一点:我们通过这种方式避免了死锁(std::lock
的实现使得死锁不会发生)。
What have they gained by deferred lock here?
延迟锁定会导致无法立即获取它。这很重要,因为如果他们 这样做了 ,他们就会在没有任何防止死锁的保护的情况下这样做(随后的 std::lock
然后实现)。
关于避免死锁(参见std::lock):
Locks the given Lockable objects lock1, lock2, ..., lockn using a deadlock avoidance algorithm to avoid deadlock.
The objects are locked by an unspecified series of calls to lock, try_lock, and unlock. [...]
旁注:另一个更简单的避免死锁的算法总是用 e 锁定银行账户。 G。首先是较低的帐号 (AN)。如果一个线程正在等待更高 AN 的锁,那么持有它的另一个线程要么已经获得了两个锁,要么正在等待第二个——它不能是第一个线程之一,因为它必须有更高的 AN .
对于任意数量的线程,这并没有太大变化,任何持有较低锁的线程都在等待较高的锁,如果也持有的话。如果你绘制一个有向图,边从 A 到 B,如果 A 正在等待 B 持有的第二个锁,你将得到一个(多)树结构,但你永远不会有圆形子结构(这表明死了锁)。