std::shared_ptr本身非线程安全,多线程同时赋值或修改会引发数据竞争;其引用计数增减原子,但所指对象及指针变量自身读写需额外同步。

std::shared_ptr 本身不是线程安全的“共享变量”
多个线程同时对同一个 std::shared_ptr 变量做赋值、reset() 或移动操作,会触发数据竞争——C++17 及以前标准中,这种写操作**不保证原子性**。比如两个线程同时执行 ptr = std::make_shared<int>(42)</int>,可能导致控制块损坏或未定义行为。
常见错误现象:
- 程序偶发崩溃,堆栈显示在
shared_ptr构造/析构内部出错 -
use_count()返回异常值(如负数、远大于预期) - 对象被重复释放或提前释放
解决方法:
- C++20 起可用
std::atomic<:shared_ptr>></:shared_ptr>替代普通变量,支持原子读写和load()/store() - C++17 及之前必须用互斥锁保护该变量的读-改-写全过程,例如封装成带锁的 setter/getter
- 避免让多个线程直接持有同一
shared_ptr变量的引用;优先让各线程持有一份独立拷贝(拷贝本身是线程安全的)
shared_ptr 指向的对象访问必须单独加锁
std::shared_ptr 的引用计数增减是原子的,但**它完全不管所指对象内部的数据是否线程安全**。这是最常被忽略的一层隔离。
使用场景:
- 多个线程通过各自持有的
shared_ptr访问同一底层对象(如shared_ptr<mydata></mydata>) - 对象内含非原子成员(如
int counter、std::vector等)
正确做法:
- 为该对象配套一个
std::mutex,所有读写操作前加锁 - 不要把锁藏在对象内部再暴露裸指针——那会破坏封装且易漏锁
- 若只读多、写少,可考虑
std::shared_mutex(C++17)实现读写分离
示例错误写法:
auto p = g_ptr; // g_ptr 是全局 shared_ptr<int><br>++(*p); // 危险!无同步</int>
weak_ptr::lock() 是线程安全的观察入口
当需要从多个线程安全判断一个资源是否还存活时,std::weak_ptr::lock() 是唯一推荐方式。它在控制块上执行原子操作:若对象尚存,返回有效的 shared_ptr;否则返回空。
为什么不用 expired() + 手动构造?
-
expired()只检查一次状态,之后到lock()之间可能已被销毁 → 悬空指针 -
lock()是原子的“检查+升级”,一步到位
典型用法:
- 异步回调中检查资源是否仍有效:
if (auto sp = wp.lock()) { use(*sp); } - 避免循环引用后,在子对象中用
weak_ptr观察父对象生命周期 - 不适用于高频调用场景——每次
lock()都涉及原子读,有轻微开销
别把 shared_ptr 当作“自带锁”的万能句柄
智能指针解决的是**生命周期管理**问题,不是**并发访问控制**问题。混淆这两者会导致严重隐患:
- 误以为多个线程拷贝同一个
shared_ptr就等于“自动线程安全” → 忽略对象内部同步 - 在类接口中返回
shared_ptr<t></t>的引用或裸指针 → 外部绕过封装直接操作,锁失效 - 用
std::shared_ptr<:mutex></:mutex>试图“共享锁” → 锁对象本身不能跨线程共享,应每个线程持有一份指向同一 mutex 实例的指针
真正关键的边界在于:引用计数安全 ≠ 对象数据安全 ≠ 指针变量自身读写安全。三者必须分别审视、分别保护。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











