我正在使用一段涉及银行账户转账的多线程代码。目标是在账户之间安全地转账,而不会遇到竞争条件。我使用
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);
}
}
std::lock
的目的是提供多个Lockable对象的无死锁锁定(参见libc++实现)。
经典问题是,如果你有两把锁L1和L2,以及
那么可能会出现死锁,因为每个线程都可以持有一个锁,并需要另一个线程的另一个锁。
如果程序中唯一锁定
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
。
std::scoped_lock
: 事情会变得更加简单
std::scoped_lock lock(from.mtx, to.mtx);