为什么我要在一个功能中锁定两个互斥锁-延迟锁定也要锁定吗?

时间:2018-08-17 07:16:03

标签: c++ std mutex

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

一次锁定两个互斥锁会得到什么?
他们通过在这里延迟锁定获得了什么?

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

4 个答案:

答案 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 Hodgesanswer

  

一次锁定两个互斥锁会得到什么?

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)

fromto2个帐户,可以分别在应用程序中的任何地方使用。

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

lock_guard将在从功能中退出时释放互斥锁。

答案 3 :(得分:0)

银行帐户数据结构的每个帐户都有一个锁。

将资金从一个帐户转移到另一个帐户时,我们需要锁定两个帐户(因为我们要从一个帐户中删除资金并将其添加到另一个帐户中)。我们希望此操作不会死锁,因此请使用std::lock一次锁定两者,因为这样做可以确保没有死锁。

完成交易后,我们需要确保释放锁。该代码使用RAII来完成。通过adopt_lock标签,我们使对象采用已经锁定的互斥锁(当lock1超出范围时将释放该互斥锁)。 使用defer_lock标签,我们为当前未锁定的互斥锁创建一个unique_lock,目的是稍后对其进行锁定。同样,当unique_lock超出范围时,它将被解锁。