5

I am having some trouble conceptualizing how unique_lock is supposed to operate across threads. I tried to make a quick example to recreate something that I would normally use a condition_variable for.

#include <mutex> #include <thread> using namespace std; mutex m; unique_lock<mutex>* mLock; void funcA() { //thread 2 mLock->lock();//blocks until unlock?Access violation reading location 0x0000000000000000. } int _tmain(int argc, _TCHAR* argv[]) { //thread 1 mLock = new unique_lock<mutex>(m); mLock->release();//Allows .lock() to be taken by a different thread? auto a = std::thread(funcA); std::chrono::milliseconds dura(1000);//make sure thread is running std::this_thread::sleep_for(dura); mLock->unlock();//Unlocks thread 2's lock? a.join(); return 0; } 
4
  • If you would normally need condition variable, than you need condition variable. unique_lock is just abstraction for mutex ownership. All synchronization it does is locking and unlocking mutex. Commented Sep 21, 2013 at 21:10
  • @zch This is purely to understand what is going on :-) Commented Sep 21, 2013 at 21:13
  • 3
    unique_lock is not meant to be shared, only to hold a lock across a specific scope (it's just a RAII wrapper for the mutex lock, nothing more). Of course you can try to misuse it, and to be blunt as far as misuse goes I think you really nailed it. ;) Commented Sep 21, 2013 at 21:27
  • 1
    A mutex is per resource (global); a lock is per thread. Commented Sep 21, 2013 at 21:27

4 Answers 4

12

unique_lock should not be accessed from multiple threads at once. It was not designed to be thread-safe in that manner. Instead, multiple unique_locks (local variables) reference the same global mutex. Only the mutex itself is designed to be accessed by multiple threads at once. And even then, my statement excludes ~mutex().

For example, one knows that mutex::lock() can be accessed by multiple threads because its specification includes the following:

Synchronization: Prior unlock() operations on the same object shall synchronize with (4.7) this operation.

where synchronize with is a term of art defined in 4.7 [intro.multithread] (and its subclauses).

Sign up to request clarification or add additional context in comments.

3 Comments

Wow, this was killing me. (Had been trying to use a manually .unlock()ed unique_lock across threads because the condition_variable demands it. Took a while to figure out that the foot gun protector is itself a large foot gun. Nobody but myself to blame though.)
Is there a reference to this anywhere? According to cppreference, std::unique_lock<std::mutex> will satisfy the requirements of Lockable and can be used with std::lock (just like a mutex, i.e. unique_lock is a superset of a mutex). I can't find anything that defines if this is meant to only be from one thread at a time or not.
In the standard, 4.7.1 Data races [intro.races] goes into excruciating detail about thread safety issues. In a nutshell it defines synchronization operations. These are identified throughout the std::lib with Synchronization clauses on the specification of certain operations. For example mutex::lock() has a synchronization clause. Somewhere (maybe under [intro.races] it is also says that simultaneous operations that are const (and in the absence of non-const) are also thread-safe. Everything else is not designed to be called by more than one thread at a time.
5

That doesn't look at all right. First, release is "disassociates the mutex without unlocking it", which is highly unlikely that it is what you want to do in that place. It basically means that you no longer have a mutex in your unique_lock<mutex> - which will make it pretty useless - and probably the reason you get "access violation".

Edit: After some "massaging" of your code, and convincing g++ 4.6.3 to do what I wanted (hence the #define _GLIBCXX_USE_NANOSLEEP), here's a working example:

#define _GLIBCXX_USE_NANOSLEEP #include <chrono> #include <mutex> #include <thread> #include <iostream> using namespace std; mutex m; void funcA() { cout << "FuncA Before lock" << endl; unique_lock<mutex> mLock(m); //thread 2 cout << "FuncA After lock" << endl; std::chrono::milliseconds dura(500);//make sure thread is running std::this_thread::sleep_for(dura); //this_thread::sleep_for(dura); cout << "FuncA After sleep" << endl; } int main(int argc, char* argv[]) { cout << "Main before lock" << endl; unique_lock<mutex> mLock(m); auto a = std::thread(funcA); std::chrono::milliseconds dura(1000);//make sure thread is running std::this_thread::sleep_for(dura); //this_thread::sleep_for(dura); mLock.unlock();//Unlocks thread 2's lock? cout << "Main After unlock" << endl; a.join(); cout << "Main after a.join" << endl; return 0; } 

Not sure why you need to use new to create the lock tho'. Surely unique_lock<mutex> mlock(m); should do the trick (and corresponding changes of mLock-> into mLock. of course).

Comments

3

A lock is just an automatic guard that operates a mutex in a safe and sane fashion.

What you really want is this code:

std::mutex m; void f() { std::lock_guard<std::mutex> lock(m); // ... } 

This effectively "synchronizes" calls to f, since every thread that enters it blocks until it manages to obtain the mutex.

A unique_lock is just a beefed-up version of the lock_guard: It can be constructed unlocked, moved around (thanks, @MikeVine) and it is itself a "lockable object", like the mutex itself, and so it can be used for example in the variadic std::lock(...) to lock multiple things at once in a deadlock-free way, and it can be managed by an std::condition_variable (thanks, @syam).

But unless you have a good reason to use a unique_lock, prefer to use a lock_guard. And once you need to upgrade to a unique_lock, you'll know why.

11 Comments

+1. In addition to what you just said, the only reason I have found so far to prefer unique_lock over lock_guard is because condition_variable requires a unique_lock. That concerns at the very least 10mloc (just so OP gets an idea). Admittedly only a very small part is C++11-ready but that doesn't change the underlying concepts and requirements.
@syam: Good point about condition_variable, thanks! (It's because the wait call needs to lock and unlock, which the simple lock_guard doesn't provide.)
Another useful feature of unique_lock is its moveable, so you can say: auto stackLock = Lock(); which is a whole less wordy than std::lock_guard<std::mutex> stackLock(m_lock); [edit where Lock is a member function which returns std::unique_lock<std::mutex>(m_lock)]
@MikeVine: Yeah... I'd generally say that by the time you're transferring ownership of locks, you're probably in fairly deep anyway, but your example is a pretty good idea.
|
0

As a side-note, the above answers skip over the difference between immediate and deferred locking of mutex:

#include<mutex> ::std::mutex(mu); auto MyFunction()->void { std::unique_lock<mutex> lock(mu); //Created instance and immediately locked the mutex //Do stuff.... } auto MyOtherFunction()->void { std::unique_lock<mutex> lock(mu,std::defer_lock); //Create but not locked the mutex lock.lock(); //Lock mutex //Do stuff.... lock.unlock(); //Unlock mutex } 

MyFunction() shows the widely used immediate lock, whilst MyOtherFunction() shows the deferred lock.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.