c++multithreadingsimultaneousownershipdestruction

How to safely destroy an object, that is frequently accessed by two different threads?


I currently ran into the Problem that an Object (Instance), that is frequently accessed by two different threads, has to be freed. For me it does not really matter which of the two threads is destructing the instance but i would prefer the one, that also creates it, although i think this does not matter at all.

So in a scenario where the thread that is supposed to destroy the object, detects that it should be deleted, and while calling the destructor, the other thread is accessing a member (function) of that object, probably some sort of runtime error will occur.

I did some research on this topic, but i could just figure out people saying: "Why deleting an Object that is still needed to exist". But in my case it should stop being needed after the piece of code that one thread is executing decides to destroy it.

I would appreciate an answer, like a hint to a nice book or article which covers this topic, but feel free to write how you would solve this problem.


Solution

  • You would need a double indirection, to manage concurrent access to the data:

    class Object {
        public;
        class Data {
            int get_value() const;
            void set_value(int);
        };
    
        class Guard {
            public:
            Guard(Nutex& mutex, Data* data) 
            :   data(data)
            {
                if( ! data) throw std::runtime_error("No Data");
                mutex.lock();
            }
    
            ~Guard() 
            {
                mutex.unlock();
            }
    
            Data& data() { return *m_data; }
    
            private:
            Data* data;
        };
    
        class ConstGuard {
            public:
            ConstGuard(Mutex& mutex, const Data* data) 
            :   data(data)
            {
                if( ! data) throw std::runtime_error("No Data");
                mutex.lock();
            }
    
            ~ConstGuard() 
            {
                mutex.unlock();
            }
    
            const Data& data() const { return *m_data; }
    
            private:
            const Data* data;
        };
    
        private:
        friend std::shared_ptr<Object> make_object(const Data&);
        Object(const Data& data)
        :   data(new Data(data))
        {}
    
        public:
        ~Object()
        {
            delete data;
        }
    
        /// Self destruction.
        void dispose() {
            Guard guard(get());
            delete data;
            data = 0;        
        }
    
        // For multiple operations use a Guard.
        Guard get() { return Guard(mutex, data); }
        ConstGuard get() const { return ConstGuard(mutex, data); }
    
        // For single operations you may provide convenience functions.
        int get_value() const { return ConstGuard(mutex, data).data().get_value(); }
        void set_value(int value) { Guard(mutex, data).data().set_value(value); }
    
        private:
        mutable Mutex mutex;
        data* data;
    };
    
    std::shared_ptr<Object> make_object(const Object::Data& data) {
        return std::make_shared<Object>(data);
    }
    

    (Note: The above code is just a sketch, I have not compiled it)

    That was the long story. The short one is [20.7.2.5] shared_ptr atomic access:

    Concurrent access to a shared_ptr object from multiple threads does not
    introduce a data race if the access is done exclusively via the functions
    in this section and the instance is passed as their first argument.