std::lock_guard or std::scoped_lock?
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 acondition_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).
std::scoped_lock or std::unique_lock or std::lock_guard?
The two objects are for different purposes. scoped_lock
is for the simple case of wanting to lock some number of mutex objects in a deadlock-free way. Locking a single mutex is just a special case of locking multiple ones. The object is completely immobile, and it's very simple.
unique_lock
provides a number of features, few of which are especially applicable when simultaneously locking multiple mutexes.
Deferred locking. Deferring would have to be all or nothing; you either defer locking all the mutexes or none of them. It's not clear why you would want to defer locking a series of mutexes, since you would have to relinquish any locks that succeeded if any of them failed.
Timeout locks. If you want a timeout of 100ms, does that mean that locking all of the mutexes should take no more than 100ms? That is, if the first 3 lock immediately, but the next one takes 75ms, should it be considered a timeout if the fifth takes 30ms?
Adoption of mutexes. The whole point of locking multiple mutexes in a single operation is to be able to avoid deadlocks. This is done by locking the mutexes in an order that is globally consistent. That is, any place where you lock those mutex objects with
std::lock
equivalent calls will lock them in the same order, no matter what.If one of the mutexes has already been locked (and thus the lock should be adopted), then it was locked outside of
std::lock
, and thus you have no guarantee that it was locked in the globally consistent order. And that ignores the difficulty of specifying which mutexes to adopt and which ones to lock.Transfer of ownership (being moveable). This is a dubious prospect for multiple mutexes for similar reasons as adopting locks. The guarantees against deadlocks only work if a single call to
std::lock
or equivalent locks all of the mutexes of interest. If you're moving ownership of thesescoped_lock
s around, it becomes very easy to be at a point in code where you have multiplescoped_lock
s in the same scope, when you could have locked all of them in one go. This courts the very kind of deadlock thatstd::lock
was created to avoid.
Note that std::lock
(the basis of scoped_lock
's functionality) doesn't even try to provide any of these features.
Could there be a specialization of scoped_lock
which took only one mutex type that offered the behavior of unique_lock
? Sure. But that would violate the purpose of scoped_lock
, which was to be a deadlock-safe locker for multiple mutexes. It only obsoleted lock_guard
by accident, since it had the identical interface in the case of a single mutex.
Besides, having template specializations with vastly different interfaces and capabilities doesn't usually work out well. See vector<bool>
as an example.
Should I use lock_guard, scoped_lock or unique_lock in this situation?
To summarize what was already written in comments:
Yes, the code is correct. However, it may be inefficient, because it disallows reading from any array element while writing to another array element. You might want to do more fine-grained synchronization by using a mutex
for each array element.
class Blah
{
const int numDevices = getDevices();
std::vector<std::mutex> mutexes;
std::vector<StateInfo> stateInfo;
public:
Blah() : mutexes(numDevices), stateInfo(numDevices){}
void writers(StateInfo &newSi, const int i)
{
std::lock_guard<std::mutex> guard(mutexes[i]);
stateInfo[i] = newSi;
}
StateInfo reader(const int i)
{
std::lock_guard<std::mutex> guard(mutexes[i]);
return stateInfo[i];
}
};
Why c++11 std::lock and std::scoped_lock need at least 2 parameters?
It doesn't require two, it can lock one or more.
From the cppreference page you took your example from (emphasis mine):
The class scoped_lock is a mutex wrapper that provides a convenient RAII-style mechanism for owning one or more mutexes for the duration of a scoped block.
std::scoped_lock
is a convenience utility for acquiring multiple mutexes - it will use deadlock avoiding mechanism under the hood. In C++11 and C++14 we only had std::lock()
, but it is not a RAII mechanism (it will not unlock mutexes automatically).
You can also use std::scoped_lock
with single mutex, then it becomes equivalent to std::lock_guard
Why put std::lock before std::lock_guard
Why do we apply the std::lock and then apply 2 std::lock_guards with std::adopt_lock instead of just applying 2 std::lock_guards one after another??
If you used two std::lock_guard
without std::lock
the order of locking for swap(a, b);
would be the opposite of swap(b, a);
, where a
and b
are X
s. If one thread tried swap(a, b);
while another tried swap(b, a);
they could deadlock. The first thread would own the lock on a
's mutex and wait for b
's while the second thread would own the lock on b
's mutex and wait for a
's. Using std::lock
ensures that the locking order is always consistent.
Why cant we just put this 2 std::mutexes in the std::scoped_lock??
If you look at the date of publication for the article you linked, c++17 did not exist yet. Since std::scoped_lock
was introduced by c++17, it could not have been used in the article. This kind of locking problem is what std::scoped_lock
is design to solve and should be used in modern code.
std::scoped_lock behaviour with a single mutex
If you read the specification of lock_guard
(which is right above scoped_lock
) it should be clear.
[thread.lock.guard]-3
Initializes pm with m. Calls m.lock()
[thread.lock.scoped]-3
Initializes pm with tie(m...). [...] Otherwise if sizeof...(MutexTypes) is 1, then m.lock(). [...]
It doesn't explicitly mention to use lock_guard
but it is required to have the same behavior.
scoped_lock inside lock_guard, is it redundant?
They lock different mutexes. Whether this makes sense depends on what is do something
. For example it could be:
void B(){
boost::mutex::scoped_lock b_lock(b_mutex);
/* do something that needs b_mutex locked */
}
void A(){
const std::lock_guard<std::mutex> a_lock(a_mutex);
/* do something that needs a_mutex locked */
B();
}
It seems like A
could be changed to
void A(){
{
const std::lock_guard<std::mutex> a_lock(a_mutex);
/* do something that needs a_mutex locked */
}
B();
}
But whether this is still correct depends on details that were left out from the posted code.
Locking two different mutexes is not redundant, because other threads may lock only one of them.
std::scoped_lock and mutex ordering
The order for std::lock
isn't defined until run-time, and it is not fixed. It is discovered experimentally by the algorithm for each individual call to std::lock
. The second call to std::lock
could lock the mutexes in a different order than the first, even though both calls might use the same list of mutexes in the same order at the call site.
Here is a detailed performance analysis of several possible implementations of std::lock
: http://howardhinnant.github.io/dining_philosophers.html
Using a fixed ordering of the mutexes is one of the algorithms that is performance-compared in the above link. It is not the best performing algorithm for the experiments conducted.
The libstdc++ implementation the OP points to is a high quality implementation of what the analysis labels "Smart & Polite"
std::unique_lock std::mutex or std::lock_guard std::mutex ?
The difference is that you can lock and unlock a std::unique_lock
. std::lock_guard
will be locked only once on construction and unlocked on destruction.
So for use case B you definitely need a std::unique_lock
for the condition variable. In case A it depends whether you need to relock the guard.
std::unique_lock
has other features that allow it to e.g.: be constructed without locking the mutex immediately but to build the RAII wrapper (see here).
std::lock_guard
also provides a convenient RAII wrapper, but cannot lock multiple mutexes safely. It can be used when you need a wrapper for a limited scope, e.g.: a member function:
class MyClass{
std::mutex my_mutex;
void member_foo() {
std::lock_guard<mutex_type> lock(this->my_mutex);
/*
block of code which needs mutual exclusion (e.g. open the same
file in multiple threads).
*/
//mutex is automatically released when lock goes out of scope
}
};
To clarify a question by chmike, by default std::lock_guard
and std::unique_lock
are the same.
So in the above case, you could replace std::lock_guard
with std::unique_lock
. However, std::unique_lock
might have a tad more overhead.
Note that these days (since, C++17) one should use std::scoped_lock
instead of std::lock_guard
.
Related Topics
What Exactly Is the L Prefix in C++
Qt - Updating Main Window with Second Thread
Convert Windows Filetime to Second in Unix/Linux
In C++, Is It Safe/Portable to Use Static Member Function Pointer for C API Callbacks
Qt: Resizing a Qlabel Containing a Qpixmap While Keeping Its Aspect Ratio
Is the Order of Iterating Through Std::Map Known (And Guaranteed by the Standard)
How to Make Std::Vector's Operator[] Compile Doing Bounds Checking in Debug But Not in Release
Undefined Symbols "Vtable for ..." and "Typeinfo For..."
Regex Replace with Callback in C++11
How to Compile Qt 5 Under Windows or Linux, 32 or 64 Bit, Static or Dynamic on Visual Studio or G++
C++ Convert from 1 Char to String
What Default Promotions of Types Are There in the Variadic Arguments List
How to Convert Euler Angles to Directional Vector
Writing Universal Memoization Function in C++11
How to Force Linker to Use Shared Library Instead of Static Library