std::lock 如何与 std::unique_lock 对象一起使用,而不是直接与 std::mutex 一起使用?

Sam*_*ami 3 c++ multithreading deadlock unique-lock

我正在使用一段涉及银行帐户转账的多线程代码。目标是在账户之间安全地转账,而不会遇到竞争条件。我用来std::mutex在转账过程中保护银行账户余额:

std::unique_lock我的问题集中在with的使用上std::lock。我没有将std::mutex对象直接传递给std::lock,而是将它们包装起来std::unique_lock并将它们传递给std::lock

如何std::lock与对象一起工作std::unique_lock

负责std::lock实际锁定fromto互斥锁,而std::unique_lock对象仅管理锁(即,当它们超出范围时释放它们)?

是否std::lock调用lock()的方法std::unique_lock

std::unique_lockstd::lock直接将std::mutex对象传递给相比,使用 with 有何优点std::lock

struct bank_account
{
    bank_account(int balance) :
        mtx(), balance{ balance }

    {}
    std::mutex mtx;
    int balance;
};

void transfer(bank_account& from, bank_account& to, int amount)
{
    std::unique_lock<std::mutex> from_Lock(from.mtx, std::defer_lock);
    std::unique_lock<std::mutex> to_Lock(to.mtx, std::defer_lock);
    std::lock(from_Lock, to_Lock);
    
    if (amount <= from.balance)
    {
        std::cout << "Before:    " << amount << " from: " << from.balance << " to: " << to.balance << '\n';
        from.balance -= amount;
        to.balance += amount;
        std::cout << "After:     " << amount << " from: " << from.balance << " to: " << to.balance << '\n';
    }
    else
    {
        std::cout << amount << " is greater than " << from.balance << '\n';
    }
}

int main()
{
    bank_account A(200);
    bank_account B(100);
    std::vector<std::jthread> workers;
    workers.reserve(20);
    for (int i = 0; i < 10; ++i)
    {
        workers.emplace_back(transfer, std::ref(A), std::ref(B), 20);
        workers.emplace_back(transfer, std::ref(B), std::ref(A), 10);
    }
}
Run Code Online (Sandbox Code Playgroud)

Jan*_*tke 5

目的std::lock是提供多个Lockable对象的无死锁锁定(参见libc++实现) 。经典问题是,如果您有两个锁L1L2,并且

  • 一个线程锁定L1然后锁定L2,并且
  • 另一个线程锁定L2然后锁定L1

那么可能会出现死锁,因为每个线程都可以持有一个锁,并需要另一个线程的另一个锁。当您锁定from.mtxto.mtx进入以下情况时,会出现此问题:

std::unique_lock<std::mutex> from_Lock(from.mtx, std::defer_lock);
std::unique_lock<std::mutex> to_Lock(to.mtx, std::defer_lock);
std::lock(from_Lock, to_Lock);
Run Code Online (Sandbox Code Playgroud)

std::lockfrom_Lock执行和的无死锁锁定to_Lock,并std::unique_lock执行其余操作(即 RAII 内容)。

问答

如何std::lock与对象一起工作std::unique_lock
是否std::lock调用lock()的方法std::unique_lock

std::unique_lockLockable,并且std::lock会调用lock()它,然后它lock()是互斥体。

负责std::lock实际锁定fromto互斥锁,而std::unique_lock对象仅管理锁(即,当它们超出范围时释放它们)?

std::unique_lock完全有能力自行锁定和解锁互斥锁。它唯一不能做的是在涉及多个锁时实现无死锁锁定。

std::unique_lockstd::lock直接将std::mutex对象传递给相比,使用 with 有何优点std::lock

之后您必须手动解锁两个互斥体,这很容易出现错误。std::unique_ptr这是与vs. new/类似的问题delete。如果您立即将两个互斥体包装在一个 while 中,那就没问题了std::lock_guard

进一步改进

为了与 一起使用std::lock,您可以使用比以下更简单的锁std::unique_lock

std::lock(from.mtx, to.mtx);
std::lock_guard<std::mutex> from_lock(from.mtx, std::adopt_lock);
std::lock_guard<std::mutex> to_lock(to.mtx, std::adopt_lock);
Run Code Online (Sandbox Code Playgroud)

std::unique_lock仅当您想转让所有权时才需要;否则你可以使用std::lock_guard(这是一个稍微简单的类型)。

如果您使用 C++17,事情会变得更加简单std::scoped_lock

// CTAD, equivalent to std::scoped_lock<std::mutex, std::mutex> lock(...)
std::scoped_lock lock(from.mtx, to.mtx);
Run Code Online (Sandbox Code Playgroud)

std::scoped_lock是构造函数中内置的无死锁锁定的替代品std::lock_guard,类似于使用std::lock.


另请参阅锁定多个 std::mutex 的最佳方法是什么?