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.
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.