std::shared_ptr 的线程安全性

发布于:2025-06-22 ⋅ 阅读:(23) ⋅ 点赞:(0)

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;(赋值操作竞争)

关键机制解析

  1. 原子引用计数的实现

    • 控制块(control block)中的计数器使用原子类型

    • 增减操作通过 CPU 级原子指令实现(如 x86 的 LOCK XADD

    • 保证即使多线程同时拷贝/析构,引用计数也准确

  2. 内存序的作用:

    memory_order_acq_rel 在减少计数时确保:删除对象前,所有线程对对象的操作已完成;避免指令重排序导致对象提前释放
  3. 对象删除安全性
    // 删除对象时的保护逻辑
    if (ref_count.fetch_sub(1) == 1) {  // 原子减后判断
        // 只有最后一个持有者执行删除
        delete ptr; 
    }


线程安全使用指南

  1. 安全模式

    // 多线程共享只读对象
    std::shared_ptr<const Config> config = load_config();
    // 所有线程安全读取 config->getValue()
  2. 需加锁场景

    std::shared_ptr<Data> resource;
    std::mutex mtx;
    
    void update() {
        std::lock_guard<std::mutex> lk(mtx);
        resource = std::make_shared<Data>(new_data); // 安全更新
    }
  3. 高效跨线程传递

    // 使用 std::move 避免原子操作开销
    std::shared_ptr<Job> job = create_job();
    std::thread worker([job = std::move(job)] {
        // 独占使用,无引用计数开销
    });
  4. 弱引用解决循环依赖

    std::weak_ptr<Node> weak_node = node;
    if (auto locked = weak_node.lock()) { // 原子检查
        // 安全使用 locked
    }

网站公告

今日签到

点亮在社区的每一天
去签到