c++multithreadingthread-safetyshared-ptrweak-ptr

Update if not Expired on Weak Pointer in Multi-Threaded Context


The client holds a shared pointer to a value say double, and a server updates this value on another detached thread by holding a weak_pointer, the server checks if the weak_pointer is expired and if it is not, it is deleted from a safe vector. I suspect this would not work as the shared_ptr could be destructed from the client side between my read of expired() (which I think is thread safe as I imagine it refers to the atomic counter of shared_ptr) and my update of the value. Is there a way to have the check expired() and a lambda function updating the value before it is destructed please? I mean:

struct server
{
public:
    static void subscribe(const std::shared_ptr<double>& d)
    {
        m_values.safe_push_back(d); //safely pushes back in the vector
    }

    void update()
    {
        auto updater = [](std::weak_ptr<double>& wd)
        {
            if(wd.expired()) wd = nullptr;
            else *wd += 2.0; //This is not thread safe I guess?
        };
        m_values.safe_remove_all(nullptr);
        m_values.safe_visit(updater);
    };

private:
    static safe_vector<std::weak_ptr<double>> m_values;
}
struct client
{
    void subcribe_sleep_print(const double random_seconds)
    {
        std::shared_ptr<double> d = std::make_shared<double>(0.0); //for the sake of the example assume we create subscribe before sleep
        server::subscribe(d);
        sleep_for_seconds(random_seconds); //sleeps for some random time in seconds.
        std::cout << *d << std::endl;
    }
}

Imagine server::update and client::subcribe_sleep_print are running on different threads. This is not safe as the shared_ptr might get destructed while the server is writing? Is there a way to this with no user (me) defined atomics or mutexes? I don't mind if it's used in the background as long as I don't add them (atomic/mutex) myself as I know the shared pointer uses an atomic counter already.

EDIT: I am not using double in my program, I am using a thread safe object :) So the operation += can be assumed thread safe. Sorry.


Solution

  •         else *wd += 2.0; //This is not thread safe I guess?
    

    No, it's not thread-safe. You are facing a race-condition where the pointer may expire after all just after you have checked for expiration. In that case, you have now dereferenced a nullptr.

    So the way to go is to explictly use std::weak_ptr<T>::lock() to try and obtain the associated shared pointer (which is also what expired() did internally), and only if that obtained a valid pointer you may do a read- or write-access.

    If you didn't obtain a valid shared pointer, treat it just as if expired() had returned false.

       m_values.safe_remove_all(nullptr);
       m_values.safe_visit(updater);
    

    I'm certain you've got the order wrong. You had intended to first update, and then weed out the nullptr your update callback had produced.