Suppose we have whole-part object relationship. For instance, there is a Boss
that can own multiple Worker
s.
For a Boss
, we want the following operations:
- add a new
Worker
to its "collection" - transfer an existing
Worker
object to anotherBoss
object
I provide some C++ code that illustrates this idea below (some functions were skipped).
class Worker {
private:
Boss *owner = nullptr;
std::string name;
public:
Worker(std::string name) : name(name), owner(nullptr) { }
Boss* getOwner() const {
return this->owner;
}
void setOwner(Boss *owner) {
this->owner = owner;
}
};
class Boss {
private:
std::set<Worker *> workers;
std::string name;
public:
Boss(std::string name) : name(name) { }
~Boss() {
for (auto worker : this->workers) {
delete worker;
}
this->workers.clear();
}
void addWorker(Worker *worker) {
this->workers.emplace(worker);
worker->setOwner(this);
}
void transferOwnership(Worker *dummyWorker, Boss *newOwner);
};
int main()
{
Boss b1{ "boss1" };
Boss b2{ "boss2" };
b1.addWorker(new Worker{ "w1" });
b1.addWorker(new Worker{ "w2" });
b2.addWorker(new Worker{ "w3" });
Worker temp{ "w2" };
b1.transferOwnership(&temp, &b2);
b1.print(); // boss: boss1 having workers: w1
b2.print(); // boss: boss2 having workers: w2 w3
return 0;
}
A serial implementation of the transferOwnership()
function would be the following:
void Boss::transferOwnership(Worker *dummyWorker, Boss *newOwner) {
// find existing worker
Worker *actualWorker = nullptr;
for (auto worker : this->workers) {
if (*worker == *dummyWorker) {
actualWorker = worker;
}
}
if (nullptr == actualWorker) {
return;
}
// remove existing worker from this
this->workers.erase(actualWorker);
// add worker to other (newOwner)
newOwner->workers.emplace(actualWorker);
actualWorker->setOwner(newOwner);
}
Now, we want to parallelize this scenario. Suppose there are multiple threads that can execute any of the available operations (addWorker()
or transferOwnership()
) on any of the existing Boss
objects.
The question is how to synchronize the code such that we always get a consistent result at the end and not get into deadlocks.
I think a solution would be to have a static mutex variable inside the Boss
class, lock it at the beginning of the operation and end it at the end. This will protect all workers
sets of all Boss
objects.
Another solution would be to have a mutex for each Boss
object. However, I think here we might encounter deadlocks as in transferOwnership()
we will need to lock/unlock the mutexes of both Boss
es and I think we cannot impose a certain locking order in this situation.
Can you think of other/better solutions?