Std::Unique_Lock<Std::Mutex> or Std::Lock_Guard<Std::Mutex>

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.

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 these scoped_locks around, it becomes very easy to be at a point in code where you have multiple scoped_locks in the same scope, when you could have locked all of them in one go. This courts the very kind of deadlock that std::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.

Does std::lock_guard release the mutex after constructed with std::adopt_lock option?

When you constructed a std::unique_lock to manage the mutex, you should stick to it unless you first break the association of the std::unique_lock with the mutex using std::unique_lock::release. In your sample, you touched the raw mutex when it's still managed by a std::unique_lock and this is wrong.

is it safe to use the same mutex with lock_gard and without it in other parts of code

Yes, you can effectively mix and match different guard instances (e.g. lock_guard, unique_lock, etc...) with std::mutex in different functions. One case I run into occassionally is when I want to use std::lock_guard for most methods, but usage of std::condition_variable expects a std::unique_lock for its wait method.

To elaborate on what Oblivion said, I typically introduce a new scope block within a function so that usage of std::lock_guard is consistent. Example:

void func2() {

{ // ENTER LOCK
lock_guard<std::mutex> lck;

//some code that should not be executed when func1 is executed

} // EXIT LOCK

// some other (thread safe) code
}

The advantage of the using the above pattern is that if anything throws an exception within the critical section of code that is under a lock, the destructor of lck will still be invoked and hence, unlock the mutex.

Difference between std::mutex lock function and std::lock_guard std::mutex ?

Using lock_guard automatically unlocks the mutex again when it goes out of scope. That makes it impossible to forget to unlock it, when returning, or when an exception is thrown. You should always prefer to use lock_guard or unique_lock instead of using mutex::lock(). See http://kayari.org/cxx/antipatterns.html#locking-mutex

lock_guard is an example of an RAII or SBRM type.

How can std::lock_guard be faster than std::mutex::lock()?

The release build produces the same result for both versions.

The DEBUG build shows ~33% longer time for func2; the difference I see in the disassembly that func2 uses __security_cookie and invokes @_RTC_CheckStackVars@8.

Are you timing DEBUG?

EDIT:
Additionally, while looking at RELEASE disassembly, I noticed that mutex methods were saved in two registries:

010F104E  mov         edi,dword ptr [__imp___Mtx_lock (010F3060h)]  
010F1054 xor esi,esi
010F1056 mov ebx,dword ptr [__imp___Mtx_unlock (010F3054h)]

and called the same way from both func1 and func2:

010F1067  call        edi  
....
010F107F call ebx


Related Topics



Leave a reply



Submit