I'm working with a piece of multithreading code that involves bank account transfers. The goal is to safely transfer money between accounts without running into race conditions. I'm using std::mutex
to protect the bank account balances during transfers:
我正在使用一段涉及银行转账的多线程代码。目标是在账户之间安全地转账,而不会遇到竞争条件。我正在使用std::Mutex在转账过程中保护银行帐户余额:
My question centers around the use of std::unique_lock
with std::lock
. Instead of passing the std::mutex
objects directly to std::lock
, I'm wrapping them with std::unique_lock
and passing those to std::lock
.
我的问题集中在std::only_lock和std::lock的使用上。我不是将std::mutex对象直接传递给std::lock,而是用std::only_lock包装它们,并将它们传递给std::lock。
How does std::lock
work with std::unique_lock
objects?
Std::lock如何与std::Unique_Lock对象一起工作?
Is std::lock
responsible for actually locking the from
and to
mutexes, while the std::unique_lock
objects merely manage the locks (i.e., release them when they go out of scope)?
Std::lock是否负责实际锁定from和to互斥锁,而std::only_lock对象只是管理锁(即,在锁超出作用域时将其释放)?
Does std::lock
call the lock()
method of std::unique_lock
?
Std::lock调用std::Unique_lock的lock()方法吗?
What is the advantage of using std::unique_lock
with std::lock
over directly passing std::mutex
objects to std::lock
?
将std::only_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);
}
}
更多回答
I did read but that's what exactly I wanted to know in the answer below which I couldn't find it in the resource: "when objects of std::unique_lock are passed to std::lock, the std::lock function calls the lock() method of each std::unique_lock object. This, in turn, locks the associated mutex. In this scenario, due to std::defer_lock, the mutex isn't locked directly upon the creation of the std::unique_lock object. Instead, it's locked via the std::lock function."
我读了,但这正是我在下面的答案中想知道的,但我在资源中找不到它:“当std::Unique_lock的对象传递给std::lock时,std::lock函数调用每个std::Unique_lock对象的lock()方法。这反过来会锁定相关的互斥锁。在这种情况下,由于std::deer_lock,互斥锁不会在创建std::Unique_lock对象时直接锁定。相反,它是通过std::lock函数锁定的。”
The purpose of std::lock
is to provide a deadlock free locking (see libc++ implementation) of multiple Lockable objects.
The classic problem is that if you have two locks L1 and L2, and
Lock的目的是提供多个可锁定对象的无死锁锁定(请参阅libc++实现)。经典的问题是,如果您有两个锁L1和L2,并且
- one thread locks L1 and then L2, and
- another thread locks L2 and then L1,
then there may be a deadlock because each thread could hold one lock and require the other from another thread. This issue applies when you're locking from.mtx
and to.mtx
in:
则可能会出现死锁,因为每个线程可能持有一个锁,并从另一个线程请求另一个锁。当您在以下位置从.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
does the deadlock-free locking of from_Lock
and to_Lock
, and std::unique_lock
does the rest (i.e. RAII stuff).
Std::Lock执行From_Lock和To_Lock的无死锁锁定,而std::Unique_Lock执行其余部分(即RAII内容)。
Q&A
How does std::lock
work with std::unique_lock
objects?
Does std::lock
call the lock()
method of std::unique_lock
?
std::unique_lock
is Lockable, and std::lock
will call lock()
on it, which then lock()
s the mutex.
Std::only_lock是可锁的,std::lock将对其调用lock(),然后锁定()S互斥锁。
Is std::lock
responsible for actually locking the from
and to
mutexes, while the std::unique_lock
objects merely manage the locks (i.e., release them when they go out of scope)?
std::unique_lock
is perfectly capable of doing locking and unlocking a mutex on its own. The only thing it can't do is implement a deadlock free locking when multiple locks are involved.
UNIQUE_LOCK完全能够单独锁定和解锁互斥锁。它唯一不能做的就是在涉及多个锁的情况下实现无死锁锁定。
What is the advantage of using std::unique_lock
with std::lock
over directly passing std::mutex
objects to std::lock
?
You would have to manually unlock both mutexes afterwards, and this is bug-prone. It's a similar problem as std::unique_ptr
vs. new
/delete
. It would be fine if you immediately wrapped both mutexes in a std::lock_guard
though.
之后,您必须手动解锁这两个互斥锁,这很容易出错。这是一个与std::only_ptr与new/Delete类似的问题。不过,如果您立即将两个互斥锁都包装在std::lock_Guard中,就可以了。
Further Improvements
For use with std::lock
, you could use a simpler lock than std::unique_lock
:
对于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);
You only need std::unique_lock
if you want to transfer ownership; otherwise you can use std::lock_guard
(which is a slightly simpler type).
如果您想转移所有权,则只需要std::only_lock;否则可以使用std::lock_Guard(这是一种稍微简单一些的类型)。
If you're using C++17, things get even simpler with std::scoped_lock
:
如果您使用的是C++17,那么使用std::scope ed_lock会让事情变得更简单:
// CTAD, equivalent to std::scoped_lock<std::mutex, std::mutex> lock(...)
std::scoped_lock lock(from.mtx, to.mtx);
std::scoped_lock
is a replacement for std::lock_guard
and has deadlock free locking built into the constructor, similar to using std::lock
.
STD::Scope_Lock是STD::Lock_Guard的替代,它在构造函数中内置了无死锁锁定,类似于使用STD::Lock。
See also What's the best way to lock multiple std::mutex'es?
另请参阅锁定多个std::mutex的最佳方式是什么?
更多回答
Thank you so much for detailed and very helpful explanations. Based on what you explained, I've come to understand that when objects of std::unique_lock are passed to std::lock, the std::lock function calls the lock() method of each std::unique_lock object. This, in turn, locks the associated mutex. In this scenario, due to std::defer_lock, the mutex isn't locked directly upon the creation of the std::unique_lock object. Instead, it's locked via the std::lock function which is designed to handle deadlock-free scenarios. Is my understanding accurate?
非常感谢您的详细和非常有帮助的解释。根据您所解释的内容,我已经理解了当std::Unique_lock的对象被传递给std::lock时,std::lock函数调用每个std::Unique_lock对象的lock()方法。这进而锁定了相关的互斥锁。在此场景中,由于std::defer_lock,互斥锁不会在创建std::only_lock对象时直接锁定。相反,它是通过std::lock函数锁定的,该函数旨在处理无死锁的情况。我的理解准确吗?
@Sami yes, that sounds right.Keep in mind that std::lock
will call a mixture of lock()
, and try_lock()
until it succeeds in locking all locks.
@Sami是的,听起来不错。请记住,std::lock将调用lock()和try_lock()的混合,直到它成功锁定所有锁。
我是一名优秀的程序员,十分优秀!