C++17 introduced a new lock class called std::scoped_lock
.
Judging from the documentation it looks similar to the already existing std::lock_guard
class.
What's the difference and when should I use it?
C++17 introduced a new lock class called std::scoped_lock
.
Judging from the documentation it looks similar to the already existing std::lock_guard
class.
What's the difference and when should I use it?
Late answer, and mostly in response to:
You can consider
std::lock_guard
deprecated.
For the common case that one needs to lock exactly one mutex, std::lock_guard
has an API that is a little safer to use than scoped_lock
.
For example:
{
std::scoped_lock lock; // protect this block
...
}
The above snippet is likely an accidental run-time error because it compiles and then does absolutely nothing. The coder probably meant:
{
std::scoped_lock lock{mut}; // protect this block
...
}
Now it locks/unlocks mut
.
If lock_guard
was used in the two examples above instead, the first example is a compile-time error instead of a run-time error, and the second example has identical functionality as the version which uses scoped_lock
.
So my advice is to use the simplest tool for the job:
lock_guard
if you need to lock exactly 1 mutex for an entire scope.
scoped_lock
if you need to lock a number of mutexes that is not exactly 1.
unique_lock
if you need to unlock within the scope of the block (which includes use with a condition_variable
).
This advice does not imply that scoped_lock
should be redesigned to not accept 0 mutexes. There exist valid use cases where it is desirable for scoped_lock
to accept variadic template parameter packs which may be empty. And the empty case should not lock anything.
And that's why lock_guard
isn't deprecated. scoped_lock
and unique_lock
may be a superset of functionality of lock_guard
, but that fact is a double-edged sword. Sometimes it is just as important what a type won't do (default construct in this case).
scoped_lock
Mike Spertus showed me such a case in the hall between meetings a couple of years ago. I'm afraid I don't remember the details well enough to replicate it. And after a quick search I don't find an example online nor in one of Mike's papers. If you desperately need an example I recommend contacting Mike directly. –
Erenow scoped_lock
, then the last with unique_lock
, retry them all if the last one fails, or do the transaction and signal the condition with the unlockable one –
Toilette condition_variable_any
waiting on a scoped_lock<M, ...>
. –
Erenow The scoped_lock
is a strictly superior version of lock_guard
that locks an arbitrary number of mutexes all at once (using the same deadlock-avoidance algorithm as std::lock
). In new code, you should only ever use scoped_lock
.
The only reason lock_guard
still exists is for compatibility. It could not just be deleted, because it is used in current code. Moreover, it proved undesirable to change its definition (from unary to variadic), because that is also an observable, and hence breaking, change (but for somewhat technical reasons).
lock_guard
. But it certainly makes the guard classes a bit easier to use. –
Codpiece scoped_lock
is NOT a strictly superior version of lock_guard
. The reason for keeping lock_guard
is not solely due to compatibility! –
Coptic scoped_lock
, which started out as a proposal to extend lock_guard
, but had to be renamed because of ABI breaking concerns. But that's a good insight. –
Codpiece The single and important difference is that std::scoped_lock
has a variadic constructor taking more than one mutex. This allows to lock multiple mutexes in a deadlock avoiding way as if std::lock
were used.
{
// safely locked as if using std::lock
std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);
}
Previously you had to do a little dance to lock multiple mutexes in a safe way using std::lock
as explained this answer.
The addition of scope lock makes this easier to use and avoids the related errors. You can consider std::lock_guard
deprecated. The single argument case of std::scoped_lock
can be implemented as a specialization and such you don't have to fear about possible performance issues.
GCC 7 already has support for std::scoped_lock
which can be seen here.
For more information you might want to read the standard paper
scoped_lock lk;
turns out to be a bug or a feature. –
Erenow scoped_lock lk; // locks all mutexes in scope
. LGTM. –
Codpiece scoped_lock lk;
is the new shorthand for scoped_lock<> lk;
. There are no mutexes. So you're right. ;-) –
Erenow scoped_lock
. It is available since C++17, where template arguments can be deduced for classes. –
Nikolai Here is a sample and quote from C++ Concurrency in Action:
friend void swap(X& lhs, X& rhs)
{
if (&lhs == & rhs)
return;
std::lock(lhs.m, rhs.m);
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
vs.
friend void swap(X& lhs, X& rhs)
{
if (&lhs == &rhs)
return;
std::scoped_lock guard(lhs.m, rhs.m);
swap(lhs.some_detail, rhs.some_detail);
}
The existence of
std::scoped_lock
means that most of the cases where you would have usedstd::lock
prior to c++17 can now be written usingstd::scoped_lock
, with less potential for mistakes, which can only be a good thing!
© 2022 - 2024 — McMap. All rights reserved.
std::unique_lock<std::mutex>
orstd::lock_guard<std::mutex>
? – Preconize