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?
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
andbool owns
(member names shown for exposition only).lock()
is simplypm->lock(); owns = true;
, andunlock
doespm->unlock(); owns = false;
. The destructor isif (owns) unlock();
. Move constructor copies over the two members, and sets them in the original tonullptr
andfalse
, correspondingly.owns_lock()
returns the value ofowns
member.All the thread-synchronizing magic is in the mutex itself, and its
lock()
andunlock()
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 calledlock()
on it), or else the program exhibits undefined behavior. This is true whether you callunlock
explicitly, or trick some helper likeunique_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.