Proper cleaning of parent and children with callbacks (C ++)

This design problem comes up again and again, and I still don't have a good solution. It can be a design template;) Only, it seems, is very C ++ specific (no garbage collection). Anyway, here is the problem:

We have a parent object that contains references to child objects. The state of a parent depends on (some aggregates) its child states. To be notified of state changes in his children, he gives them a link to himself. (In another option, he passes them a callback that children can call to notify the parent. This callback is a closure that contains a link to the parent.) The application is highly multithreaded. Now this setup is a whole nest of hornets of potential race conditions and dead locks. To understand why there is a naive implementation:

class Parent {
 public:
   Parent() {
     children_["apple"].reset(new Child("apple", this));
     children_["peach"].reset(new Child("peach", this));
   }

   ~Parent() {
   }

   void ChildDone(const string& child) {
     cout << "Child is DONE: " << child << endl;
   }

  private:
   map<string, linked_ptr<Child> > children;
}; 

class Child {
  public:
   Child(const string& name, Parent* parent) 
       : name_(name), parent_(parent), done_(false) {}

   Foo(int guess) {
     if (guess == 42) done_ = true;
     parent->ChildDone(name_);
   }

  private:
   const string name_;
   Parent* parent_;
   bool done_; 
};

Potential problems:

During the annihilation of the parent, he should be in search of ongoing callbacks from his children. Especially if these callbacks are run in a separate thread. If this is not the case, it may disappear by the time the callback is called. If there are locks in the parent and child objects (which is very likely in a multi-threaded non-trivial application), the locking order becomes a problem: the parent calls the method for the child, which in turn experiences a state transition and tries to notify the parent: dead-lock. Adding / removing children outside the constructor can be a problem if the child tries to notify the parent of its destructor. The parent must hold the lock to change the child’s map, but the child is trying to call back for the parent.

, .

, , - , , / . - , , , . : , ( , , ..), , .

+5
3

- ( , ) . , . , , , , . ​​ , () . , . .

+3

( ), issue: , , , parent: dead-lock.

, ,

  • A
  • A ,
  • B
  • , (3),

, . : (A) (B), - .

, - . , , ; , , .

. , . , .

, , , (, ), gaurentees, . , .

, " ", , , .

+1

, ChildDone, , . , , ChildDone, . ( . ?).

// Pseudocode, not compilable C++.
class Parent {

     // ....

    ~Parent() {
        mutex_.acquire();
        shuttingDown_ = true;
        mutex_.release();

        foreach (Child child in children_)
            child->detachParent();
       waitForRunningClientThreadToExit();
    }

    void ChildDone(const string& child) {
      mutex_.acquire(); 
      if (!shuttingDown_)
          cout << "Child is DONE: " << child << endl;
      mutex_.release();
    }

    bool volatile shuttingDown_ = false;
    Mutex mutex_;

    // ....

};

class Child {

    // ...

    Foo(int guess) {
       if (guess == 42) done_ = true;
       if (parent)
           parent->ChildDone(name_);
    } 

    void detachParent() {
        parent = NULL;
    }
};
+1

All Articles