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

问题描述 投票:0回答:1

我正在使用一段涉及银行账户转账的多线程代码。目标是在账户之间安全地转账,而不会遇到竞争条件。我使用

std::mutex
来保护转账期间的银行账户余额:

我的问题集中在

std::unique_lock
std::lock
的使用上。我没有将
std::mutex
对象直接传递给
std::lock
,而是用
std::unique_lock
包裹它们并将它们传递给
std::lock

std::lock
如何与
std::unique_lock
对象一起使用?

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

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

std::unique_lock
std::lock
一起使用比直接将
std::mutex
对象传递给
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);
    }
}
c++ multithreading deadlock unique-lock
1个回答
0
投票

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

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

那么可能会出现死锁,因为每个线程都可以持有一个锁,并需要另一个线程的另一个锁。

如果程序中唯一锁定

from.mtx
to.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);

...那么使用

std::lock
并不是绝对必要的,因为不可能出现这样的死锁。 即使您想使用
std::lock
,您也可以使用比
std::unique_lock
更简单的锁:

std::lock(e1.m, e2.m);
std::lock_guard<std::mutex> from_lock(from.mtx, std::adopt_lock);
std::lock_guard<std::mutex> to_lock(to.mtx, std::adopt_lock);

如果您想转让所有权,只需

std::unique_lock
;否则你可以使用
std::lock_guard

如果您使用 C++17,使用

std::scoped_lock

事情会变得更加简单
std::scoped_lock lock(from.mtx, to.mtx);
© www.soinside.com 2019 - 2024. All rights reserved.