c++ shared_ptr是否保证底层对象的线程安全性?

qaxu7uf2  于 2023-02-06  发布在  其他
关注(0)|答案(1)|浏览(158)
    • 问题**

我认为下面的代码应该会导致运行时问题,但实际上并没有。我试图在一个线程中更新shared_ptr所指向的底层对象,并在另一个线程中访问它。

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();
}
    • 说明**

奇怪的是,输出如下所示。打印新的大小为0(空),但仍然能够访问前一个向量的第一个元素。

0 is the new size
hello

我的理解是,上述代码是不是线程安全的正确?我错过了什么?

根据文件
所有成员函数(包括复制构造函数和复制赋值)都可以由shared_ptr的不同示例上的多个线程调用,而不需要额外的同步,即使这些示例是副本并共享同一对象的所有权。
既然我使用的是->/*成员函数,这是否意味着代码是线程安全的?这部分有点混乱,因为我同时执行读和写,而没有同步。

gwbalxhn

gwbalxhn1#

至于shared_ptr:一般来说,你可以从多个线程调用shared_ptr的不同示例的所有成员函数,而不需要同步。但是,如果你想从同一个shared_ptr示例上的多个线程调用这些函数,那么可能会导致争用情况。当我们在shrared_ptr的情况下谈论线程安全保证时,如上所述,它仅保证shared_ptr的内部,而不保证底层对象。
话虽如此,考虑一下下面的代码并阅读注解。你也可以在这里玩它: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(); 
}

相关问题