1. 引用计数的线程安全性
完全线程安全:
shared_ptr
的引用计数机制本身是线程安全的,这是通过原子操作实现的。实现原理:
// 伪代码示意 class control_block { std::atomic<long> ref_count; // 原子引用计数 //... }; void increment_ref_count() { ref_count.fetch_add(1, std::memory_order_relaxed); } void decrement_ref_count() { if (ref_count.fetch_sub(1, std::memory_order_acq_rel) == 1) { delete managed_object; // 安全删除对象 } }
使用
std::atomic
保证引用计数的增减是原子操作增加计数用
memory_order_relaxed
(只需原子性,无顺序约束)减少计数用
memory_order_acq_rel
(保证删除对象前的所有操作可见)
2. 指向对象的线程安全性
非线程安全:
shared_ptr
不保证其管理的对象是线程安全的// 危险示例:多个线程同时修改同一对象 std::shared_ptr<int> p = std::make_shared<int>(); void thread_func() { p->value++; // 非原子操作,数据竞争! }
需要额外同步机制(如互斥锁)保护对象内部状态。
3. shared_ptr 实例本身的线程安全性
操作类型 | 线程安全性 | 示例 |
---|---|---|
多个线程读同一实例 | ✅ 安全 | auto p2 = p1; (拷贝构造) |
多个线程写不同实例 | ✅ 安全 | p1.reset(); p2.reset(); |
多个线程写同一实例 | ❌ 不安全(需加锁) | p1 = p2; (赋值操作竞争) |
关键机制解析
原子引用计数的实现:
控制块(control block)中的计数器使用原子类型
增减操作通过 CPU 级原子指令实现(如 x86 的
LOCK XADD
)保证即使多线程同时拷贝/析构,引用计数也准确
内存序的作用:
memory_order_acq_rel
在减少计数时确保:删除对象前,所有线程对对象的操作已完成;避免指令重排序导致对象提前释放- 对象删除安全性:
// 删除对象时的保护逻辑 if (ref_count.fetch_sub(1) == 1) { // 原子减后判断 // 只有最后一个持有者执行删除 delete ptr; }
线程安全使用指南
安全模式:
// 多线程共享只读对象 std::shared_ptr<const Config> config = load_config(); // 所有线程安全读取 config->getValue()
需加锁场景:
std::shared_ptr<Data> resource; std::mutex mtx; void update() { std::lock_guard<std::mutex> lk(mtx); resource = std::make_shared<Data>(new_data); // 安全更新 }
高效跨线程传递:
// 使用 std::move 避免原子操作开销 std::shared_ptr<Job> job = create_job(); std::thread worker([job = std::move(job)] { // 独占使用,无引用计数开销 });
弱引用解决循环依赖:
std::weak_ptr<Node> weak_node = node; if (auto locked = weak_node.lock()) { // 原子检查 // 安全使用 locked }