为什么我要在一个函数中锁定两个互斥量——延迟锁定也是如此?

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;
}

他们通过同时锁定两个互斥锁获得了什么?
他们在这里通过延迟锁定获得了什么?

请解释他们做出该决定的原因。

fromto2 帐户,可以在应用程序的任何地方单独使用

通过为每个帐户设置互斥锁,您可以确保在您进行转账时没有人使用fromto个帐户。

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 持有的第二个锁,你将得到一个(多)树结构,但你永远不会有圆形子结构(这表明死了锁)。