Question

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.

Was it helpful?

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.
  • shared_ptr atomic_load(const shared_ptr* p);
  • void atomic_store(shared_ptr* p, shared_ptr r);
  • ... But I am not familiar with these functions.

OTHER TIPS

The thread which should not destroy the object should hold it using a std::weak_ptr, except for the times when it is actively using it. During these times, the weak_ptr can be upgraded to a std::shared_ptr.

The thread which should destroy it now can hold on to its shared_ptr for as long as it thinks appropriate, and discard that shared_ptr afterwards. If the other thread only has a weak_ptr (not actively using it), the object will go away. if the other thread also has a shared_ptr, it holds on to the object until the operation finishes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top