You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++ shared_ptr是否保证底层对象线程安全?代码运行存疑解析

线程安全与std::shared_ptr的困惑

问题代码

我原本以为下面的代码会触发运行时问题,但实际并没有。我在一个线程里更新std::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();
}

现象与疑问

奇怪的是程序输出如下:打印出的新size是0(空),但依然能访问原vector的第一个元素。

0 is the new size
hello

我的理解对吗?也就是这段代码并非线程安全?我是不是漏了什么?

另外,根据文档说明:

所有成员函数(包括拷贝构造函数和拷贝赋值运算符)均可由多个线程在shared_ptr的不同实例上调用,无需额外同步,即使这些实例是副本且共享同一对象的所有权。

因为我用了->/*成员函数,是不是意味着这段代码是线程安全的?这部分让我很困惑,毕竟我在没有同步的情况下同时进行读写操作。


解答

核心结论:这段代码完全不是线程安全的,你看到的正常输出只是未定义行为的侥幸表现

误区澄清

你混淆了std::shared_ptr自身的线程安全和它指向的底层对象的线程安全:

  • std::shared_ptr自身的线程安全:文档描述的是多个shared_ptr副本(比如你的p1和p2)之间的引用计数操作是线程安全的,但这仅限于shared_ptr对象本身的管理,和它指向的Foo对象无关。
  • 底层对象的线程安全:Foo中的vector<Bar>是普通容器,它的读写操作没有任何线程安全保障。你的代码里,线程t1在遍历vector,线程t2直接给vector赋值为空,这属于典型的无同步并发读写,违反C++内存模型,属于未定义行为。

为什么会出现“正常”输出?

这是未定义行为的一种巧合表现:

  1. 线程t2将vec赋值为空后,原vector占用的内存并没有立刻被系统回收或覆盖,所以t1访问旧位置的内存时,刚好还能读到之前的"hello"。
  2. 这种情况完全不可靠,更换编译器、操作系统,或者多次运行程序,大概率会出现崩溃、乱码、数据错乱等问题。

正确的线程安全做法

要保证代码安全,必须对底层对象的访问做同步控制:

  • 使用std::mutex,在读写vec前后加锁,确保同一时间只有一个线程操作vector;
  • 配合条件变量和锁,严格控制读写操作的执行顺序;
  • 对于简单数据可以考虑用std::atomic包装,但复杂容器不适用这种方式。

内容的提问来源于stack exchange,提问作者Basava Raj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 08:45:21