Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unique_lock across threads?

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;
}
like image 516
Mikhail Avatar asked Sep 21 '13 20:09

Mikhail


3 Answers

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).

like image 112
Howard Hinnant Avatar answered Oct 23 '22 01:10

Howard Hinnant


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).

like image 42
Mats Petersson Avatar answered Oct 23 '22 03:10

Mats Petersson


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.

like image 3
Kerrek SB Avatar answered Oct 23 '22 02:10

Kerrek SB