Moving a unique_lock to another thread

2019-07-07 09:35发布

问题:

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?

回答1:

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.