Basava Raj
Basava Raj

Reputation: 1

Does shared_ptr guarantee thread safety for the underlying object?

Problem

I believe the following code should lead to runtime issues, but it doesn't. I'm trying to update the underlying object pointed to by the shared_ptr in one thread, and access it in another thread.

struct Bar {
    Bar(string tmp) {
        var = tmp;
    }
    string var;
};

struct Foo {
    vector<Bar> vec;
};

std::shared_ptr<Foo> p1, p2;
std::atomic<bool> cv1, cv2;

void fn1() {
    for(int i = 0 ; i < p1->vec.size() ; i++) {
        cv2 = false;
        cv1.wait(true);
        std::cout << p1->vec.size() << " is the new size\n";
        std::cout << p1->vec[i].var.data() << "\n";
    }
}

void fn2() {
    cv2.wait(true);
    p2->vec = vector<Bar>();
    cv1 = false;
}

int main()
{
    p1 = make_shared<Foo>();
    p1->vec = vector<Bar>(2, Bar("hello"));
    p2 = p1;

    cv1 = true;
    cv2 = true;
    
    thread t1(fn1);
    thread t2(fn2);
    
    t2.join();
    t1.join();
}

Description

weirdly enough, the output is as follows. prints the new size as 0 (empty), but is still able to access the first element from the previous vector.

0 is the new size
hello

Is my understanding that the above code is not thread safe correct? am I missing something?

OR

According to the docs

All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object.

Since I'm using ->/* member functions, does it mean that the code is thread safe? This part is kind of confusing as I'm performing read and write simultaneously without synchronization.

Upvotes: 0

Views: 134

Answers (1)

bielu000
bielu000

Reputation: 2241

As for the shared_ptr: In general, you can call all member functions of DIFFERENT instances of the shared_ptr from multiple threads without synchronization. However, if you want to call these functions from multiple threads on the SAME shared_ptr instance then it may lead to a race condition. When we talk about thread safety guarantee in the case of shrared_ptr, it is only guaranteed for the internals of the shared_ptr as explained above NOT FOR THE underlying object.

Having that said, consider the following code and read the comments. You can also play with it here: https://godbolt.org/z/8hvcW19q9

#include <memory>
#include <mutex>
#include <thread>

std::mutex widget_mutex;

class Widget
{
    std::string value;

public:
  void set_value(const std::string& str) { value = str; } 
};


//This is not safe, you're calling member function of the same instance, taken by ref
void mt_reset_not_safe(std::shared_ptr<Widget>& w)
{
    w.reset(new Widget());
}

//This is safe, you have a separate instance of shared_ptr
void mt_reset_safe(std::shared_ptr<Widget> w)
{
    w.reset(new Widget());
}

//This is not safe, underlying object is not protected from race conditions
void mt_set_value_not_safe(std::shared_ptr<Widget> w)
{
    w->set_value("Test value, test value");
}

//This is safe, we use mutex to safetly update the underlying object
void mt_set_value_safe(std::shared_ptr<Widget> w)
{
    auto lock = std::scoped_lock{widget_mutex};

    w->set_value("Test value, test value");
}


template<class Callable, class... Args>
void run(Callable callable, Args&&... args)
{
    auto th1 = std::thread(callable, std::forward<Args>(args)...);
    auto th2 = std::thread(callable, std::forward<Args>(args)...);

    th1.join();
    th2.join();

}

void run_not_safe_reset()
{
    auto widget = std::make_shared<Widget>();
    run(mt_reset_not_safe, std::ref(widget));
}

void run_safe_reset()
{
    auto widget = std::make_shared<Widget>();
    run(mt_reset_safe, widget);
}

void run_mt_set_value_not_safe()
{
    auto widget = std::make_shared<Widget>();
    run(mt_set_value_not_safe, widget);
}

void run_mt_set_value_safe()
{
    auto widget = std::make_shared<Widget>();
    run(mt_set_value_safe, widget);
}

int main()
{
    //Uncommne to see the result

    // run_not_safe_reset();
    // run_safe_reset();

    // run_mt_set_value_not_safe();
    // run_mt_set_value_safe(); 
}

Upvotes: 1

Related Questions