C++ 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++内存模型,属于未定义行为。
为什么会出现“正常”输出?
这是未定义行为的一种巧合表现:
- 线程
t2将vec赋值为空后,原vector占用的内存并没有立刻被系统回收或覆盖,所以t1访问旧位置的内存时,刚好还能读到之前的"hello"。 - 这种情况完全不可靠,更换编译器、操作系统,或者多次运行程序,大概率会出现崩溃、乱码、数据错乱等问题。
正确的线程安全做法
要保证代码安全,必须对底层对象的访问做同步控制:
- 使用
std::mutex,在读写vec前后加锁,确保同一时间只有一个线程操作vector; - 配合条件变量和锁,严格控制读写操作的执行顺序;
- 对于简单数据可以考虑用
std::atomic包装,但复杂容器不适用这种方式。
内容的提问来源于stack exchange,提问作者Basava Raj
相关产品推荐
相关产品推荐

