Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving a unique_lock<recursive_mutex> to another thread

I was wondering what happens when you move a unique_lock that holds a recursive_mutex.

Specifically, I was looking at this code:

recursive_mutex g_mutex;

#define TRACE(msg) trace(__FUNCTION__, msg)

void trace(const char* function, const char* message)
{
    cout << std::this_thread::get_id() << "\t" << function << "\t" << message << endl;
}

future<void> foo()
{
    unique_lock<recursive_mutex> lock(g_mutex);
    TRACE("Owns lock");
    auto f = std::async(launch::async, [lock = move(lock)]{
        TRACE("Entry");
        TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!"); // Prints Owns lock! 
        this_thread::sleep_for(chrono::seconds(3));
    });
    TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!"); // Prints Doesn't own lock! 
    return f;
}


int main()
{
    unique_lock<recursive_mutex> lock(g_mutex);
    TRACE("Owns lock");
    auto f = foo();    
    TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!");        // Prints Owns lock! 
    f.wait();
    TRACE(lock.owns_lock()? "Owns lock!" : "Doesn't own lock!");        // Prints Owns lock!
}

The output of this sample code surprised me a lot. How does the unique_lock in main() know that the thread released the mutex? Is it real?

like image 290
Mr. Anderson Avatar asked Mar 12 '23 16:03

Mr. Anderson


1 Answers

You appear to ascribe some magic properties to unique_lock. It doesn't have any, it's a very simple class. It has two data members, Mutex* pm and bool owns (member names shown for exposition only). lock() is simply pm->lock(); owns = true;, and unlock does pm->unlock(); owns = false;. The destructor is if (owns) unlock();. Move constructor copies over the two members, and sets them in the original to nullptr and false, correspondingly. owns_lock() returns the value of owns member.

All the thread-synchronizing magic is in the mutex itself, and its lock() and unlock() methods. unique_lock is merely a thin wrapper around it.

Now, the thread that calls mutex.unlock() must, as a prerequisite, hold the mutex (meaning, that thread has previously called lock() on it), or else the program exhibits undefined behavior. This is true whether you call unlock explicitly, or trick some helper like unique_lock into calling it for you.

In light of all this, moving a unique_lock instance over to another thread is merely a recipe for triggering undefined behavior shortly thereafter; there is no upside.

like image 61
Igor Tandetnik Avatar answered Mar 20 '23 09:03

Igor Tandetnik