Consider the following:
// There are guys:
class Guy {
// Each guy can have a buddy:
Guy* buddy; // When a guy has a buddy, he is his buddy's buddy, i.e:
// assert(!buddy || buddy->buddy == this);
public:
// When guys are birthed into the world they have no buddy:
Guy()
: buddy{}
{}
// But guys can befriend each other:
friend void befriend(Guy& a, Guy& b) {
// Except themselves:
assert(&a != &b);
// Their old buddies (if any), lose their buddies:
if (a.buddy) { a.buddy->buddy = {}; }
if (b.buddy) { b.buddy->buddy = {}; }
a.buddy = &b;
b.buddy = &a;
}
// When a guy moves around, he keeps track of his buddy
// and lets his buddy keep track of him:
friend void swap(Guy& a, Guy& b) {
std::swap(a.buddy, b.buddy);
if (a.buddy) { a.buddy->buddy = &a; }
if (b.buddy) { b.buddy->buddy = &b; }
}
Guy(Guy&& guy)
: Guy()
{
swap(*this, guy);
}
Guy& operator=(Guy guy) {
swap(*this, guy);
return *this;
}
// When a Guy dies, his buddy loses his buddy.
~Guy() {
if (buddy) { buddy->buddy = {}; }
}
};
All is well so far, but now I want this to work when buddies are used in different threads. No problem, let's just stick std::mutex in Guy:
class Guy {
std::mutex mutex;
// same as above...
};
Now I just have to lock mutexes of both guys before linking or unlinking the pair of them.
This is where I am stumped. Here are failed attempts (using the destructor as an example):
Deadlock:
~Guy() { std::unique_lock<std::mutex> lock{mutex}; if (buddy) { std::unique_lock<std::mutex> buddyLock{buddy->mutex}; buddy->buddy = {}; } }When two buddies are destroyed at around the same time, it is possible that each of them locks their own mutex, before trying to lock their buddies' mutexes, thus resulting in a deadlock.
Race condition:
Okay so we just have to lock mutexes in consistent order, either manually or with
std::lock:~Guy() { std::unique_lock<std::mutex> lock{mutex, std::defer_lock}; if (buddy) { std::unique_lock<std::mutex> buddyLock{buddy->mutex, std::defer_lock}; std::lock(lock, buddyLock); buddy->buddy = {}; } }Unfortunately, to get to buddy's mutex we have to access
buddywhich at this point is not protected by any lock and may be in the process of being modified from another thread, which is a race condition.Not scalable:
Correctness can be attained with a global mutex:
static std::mutex mutex; ~Guy() { std::unique_lock<std::mutex> lock{mutex}; if (buddy) { buddy->buddy = {}; } }But this is undesirable for performance and scalability reasons.
So is this possible to do without global lock? How?