首页 > 后端开发 > C++ > 正文

C++shared_ptr与多线程环境安全使用方法

P粉602998670
发布: 2025-09-04 09:16:01
原创
406人浏览过
shared_ptr的引用计数操作线程安全,但其管理的对象及shared_ptr实例本身的并发修改需额外同步。多个线程可安全拷贝或销毁shared_ptr,因引用计数增减为原子操作;但若多线程读写shared_ptr指向的对象,则必须通过互斥锁等机制保证对象数据一致性;此外,当多个线程对同一shared_ptr变量进行赋值、重置时,该变量本身的修改非原子,C++20前需用mutex保护,C++20起可使用std::atomic<std::shared_ptr<T>>实现原子操作;weak_ptr::lock()线程安全,适合多线程中安全检查对象存活性。

c++shared_ptr与多线程环境安全使用方法

shared_ptr
登录后复制
在C++多线程环境中的安全使用,核心在于区分其自身引用计数的原子性与它所管理对象的线程安全性。简单来说,
shared_ptr
登录后复制
的引用计数操作是线程安全的,这意味着多个线程可以同时对同一个
shared_ptr
登录后复制
进行拷贝或销毁,而不会导致引用计数损坏。然而,
shared_ptr
登录后复制
所指向的对象本身,其内部数据的访问和修改,并非自动线程安全的。如果多个线程需要读写这个共享对象的数据,那么你必须为这个对象的数据访问提供额外的同步机制,比如互斥锁。此外,如果
shared_ptr
登录后复制
实例本身(而非它指向的对象)在多个线程间被赋值或重置,那么对
shared_ptr
登录后复制
实例本身的操作也需要同步。

解决方案

要安全地在多线程环境中使用

shared_ptr
登录后复制
,我们需要从几个层面来理解和实施保护:

  1. 理解

    shared_ptr
    登录后复制
    的原子性保证:
    shared_ptr
    登录后复制
    的设计确保了其内部的引用计数器在增减操作时是原子性的。这意味着,当你在一个线程中拷贝一个
    shared_ptr
    登录后复制
    ,或在另一个线程中让一个
    shared_ptr
    登录后复制
    离开作用域而递减引用计数时,这些操作都是安全的,不会出现竞争条件导致引用计数混乱。这是
    shared_ptr
    登录后复制
    在多线程环境下能够工作的基石。

  2. 保护

    shared_ptr
    登录后复制
    所管理的对象: 这是最常见的误区和挑战。
    shared_ptr
    登录后复制
    只管理对象的生命周期,它不关心对象内部的数据状态。如果多个线程会访问并修改
    shared_ptr
    登录后复制
    指向的同一个对象,那么你必须在对象内部或外部提供同步机制。

    立即学习C++免费学习笔记(深入)”;

    • 内部同步(推荐): 将互斥锁(如
      std::mutex
      登录后复制
      )作为成员变量嵌入到被管理的对象中。所有对对象数据成员的读写操作都通过这个互斥锁来保护。这样,无论
      shared_ptr
      登录后复制
      如何传递,对象自身的线程安全性都由其内部机制保证。
    • 外部同步: 如果无法修改被管理的对象,那么所有访问该对象的代码块都需要被一个外部的互斥锁保护起来。这种方式的缺点是容易遗漏,且需要调用者始终记住加锁。
    • 不可变对象: 最简单也最强大的策略之一。如果
      shared_ptr
      登录后复制
      管理的对象在创建后就不可修改(immutable),那么它天然就是线程安全的,因为不存在数据竞争的可能。
  3. 保护

    shared_ptr
    登录后复制
    实例本身: 这种情况虽然不如保护被管理对象常见,但在某些场景下至关重要。如果一个
    shared_ptr
    登录后复制
    变量本身(例如,一个全局的
    std::shared_ptr<T> current_resource;
    登录后复制
    或者一个类成员)在多个线程中被赋值重置交换,那么对这个
    shared_ptr
    登录后复制
    变量本身的操作也需要同步。

    • C++20引入了
      std::atomic<std::shared_ptr<T>>
      登录后复制
      ,可以直接对
      shared_ptr
      登录后复制
      进行原子操作。
    • 在C++20之前,或者对于更复杂的操作,你需要用
      std::mutex
      登录后复制
      来保护对
      shared_ptr
      登录后复制
      变量本身的读写。
  4. weak_ptr
    登录后复制
    在多线程中的应用:
    weak_ptr
    登录后复制
    通常用于打破
    shared_ptr
    登录后复制
    的循环引用,或者在不影响对象生命周期的情况下安全地观察对象。在多线程环境中,
    weak_ptr::lock()
    登录后复制
    操作是线程安全的,它会原子地尝试提升为
    shared_ptr
    登录后复制
    。如果对象已经销毁,
    lock()
    登录后复制
    会返回一个空的
    shared_ptr
    登录后复制
    ,这使得
    weak_ptr
    登录后复制
    成为在多线程中安全检查对象是否仍然存活的有效工具

shared_ptr
登录后复制
的引用计数在多线程下真的安全吗?它的安全边界在哪里?

是的,

shared_ptr
登录后复制
的引用计数在多线程环境下是绝对安全的。这是C++标准库设计
shared_ptr
登录后复制
时的一个核心保证。无论是通过拷贝构造函数、赋值操作符增加引用计数,还是在
shared_ptr
登录后复制
实例销毁时减少引用计数,这些操作都是原子性的。这意味着,你无需担心多个线程同时对同一个
shared_ptr
登录后复制
进行拷贝或销毁会导致引用计数器出现竞争条件,从而引发内存泄漏或过早释放的问题。

然而,它的安全边界非常明确且有限。这种线程安全仅限于引用计数器本身。它不延伸到

shared_ptr
登录后复制
所指向的对象内部的数据。举个例子,如果你有一个
std::shared_ptr<MyData> ptr;
登录后复制
,并且
MyData
登录后复制
对象内部有一个
int counter;
登录后复制
成员,多个线程通过
ptr->counter++
登录后复制
来操作,那么
counter++
登录后复制
这个操作本身并不是原子的,会引发数据竞争。
shared_ptr
登录后复制
不会神奇地让
MyData
登录后复制
对象变得线程安全。

另一个需要注意的边界是,这种原子性也不扩展到

shared_ptr
登录后复制
实例本身的并发修改。比如,你有一个全局变量
std::shared_ptr<MyData> global_ptr;
登录后复制
,如果线程A执行
global_ptr = std::make_shared<MyData>();
登录后复制
,而线程B同时执行
global_ptr = nullptr;
登录后复制
,那么对
global_ptr
登录后复制
这个变量本身的赋值操作就不是原子的,这同样会导致数据竞争,因为
global_ptr
登录后复制
在被赋值时,其内部的指针和引用计数可能会处于不一致的状态。这种情况下,你需要额外的同步机制来保护
global_ptr
登录后复制
变量本身。

如何确保
shared_ptr
登录后复制
所管理的对象在多线程环境下的数据一致性?

确保

shared_ptr
登录后复制
所管理的对象在多线程环境下的数据一致性,是使用
shared_ptr
登录后复制
时最关键也最容易出错的地方。毕竟,
shared_ptr
登录后复制
只是一个智能指针,它只负责对象的生命周期管理,而对象内部的数据状态则完全取决于你的设计。这里有几种行之有效的方法:

  1. 内部互斥锁(Mutex Guard): 这是最直接、最常用的方法。将一个

    std::mutex
    登录后复制
    作为被管理对象(比如
    MyObject
    登录后复制
    )的成员变量。所有对
    MyObject
    登录后复制
    内部数据进行修改或需要保证原子性的读取操作,都通过这个互斥锁来保护。

    class MyThreadSafeObject {
    public:
        void updateData(int value) {
            std::lock_guard<std::mutex> lock(mtx_); // 锁定互斥量
            data_ = value;
            // ... 其他数据操作
        }
    
        int getData() const {
            std::lock_guard<std::mutex> lock(mtx_); // 锁定互斥量
            return data_;
        }
    private:
        mutable std::mutex mtx_; // 使用mutable允许const成员函数锁定
        int data_ = 0;
    };
    
    // 使用
    std::shared_ptr<MyThreadSafeObject> sharedObj = std::make_shared<MyThreadSafeObject>();
    // 多个线程可以安全地调用 sharedObj->updateData() 和 sharedObj->getData()
    登录后复制

    通过这种方式,

    MyThreadSafeObject
    登录后复制
    自身就具备了线程安全性,无论它被多少个
    shared_ptr
    登录后复制
    共享,它的数据一致性都由其内部机制保证。

    RMI远程方法调用 word版
    RMI远程方法调用 word版

    Raza Microelectronics, Inc.(RMI公司)是勇于创新的信息基础架构半导体解决方案领导厂商,其产品广泛地被应用于改善不断演进的信息基础设施。在这个演进过程中,数据中心和家庭之间的连接在强度和速率方面都逐渐升级;安全和智能化已经成为每一个网络系统环境的要求;同时,边缘网络日益成为瓶颈,促使业界需要更具扩展能力及成本优势的智能网络接入方法。RMI公司为信息基础架构设计并提供多样化的解决方案,为下一代灵活的企业和数据中心应用、智能接入和数字影像系统奠定基础。 RMI远程方法调用目录 一、

    RMI远程方法调用 word版 0
    查看详情 RMI远程方法调用 word版
  2. 不可变对象(Immutable Objects): 如果你的设计允许,让

    shared_ptr
    登录后复制
    管理的对象在创建后就不能被修改。这意味着对象的所有成员变量都是
    const
    登录后复制
    的,或者只在构造函数中初始化。不可变对象天然就是线程安全的,因为不存在任何修改操作,也就没有数据竞争的可能。这是一种非常优雅且强大的解决并发问题的方法,尤其适用于配置、日志条目等场景。

    class MyImmutableData {
    public:
        MyImmutableData(int id, const std::string& name) : id_(id), name_(name) {}
        int getId() const { return id_; }
        const std::string& getName() const { return name_; }
        // 没有setter方法
    private:
        const int id_;
        const std::string name_;
    };
    
    // 使用
    std::shared_ptr<const MyImmutableData> sharedImmutableData = std::make_shared<const MyImmutableData>(1, "Test");
    // 多个线程可以安全地读取 sharedImmutableData 的数据
    登录后复制
  3. 读写锁(Shared Mutex /

    std::shared_mutex
    登录后复制
    ): 当对象读操作远多于写操作时,
    std::shared_mutex
    登录后复制
    (或
    boost::shared_mutex
    登录后复制
    )可以提供更好的性能。它允许多个线程同时进行读操作(共享锁),但在写操作时只允许一个线程进行(独占锁)。

    #include <shared_mutex> // C++17
    
    class MyReadWriteObject {
    public:
        void updateData(int value) {
            std::unique_lock<std::shared_mutex> lock(mtx_); // 独占锁
            data_ = value;
        }
    
        int getData() const {
            std::shared_lock<std::shared_mutex> lock(mtx_); // 共享锁
            return data_;
        }
    private:
        mutable std::shared_mutex mtx_;
        int data_ = 0;
    };
    登录后复制
  4. 外部同步: 如果你无法修改被管理的对象(例如,它来自第三方库),或者你认为对象内部同步会过于复杂,那么你可以在所有访问该对象的代码块外部使用一个全局或局部的

    std::mutex
    登录后复制
    来保护。这种方法要求所有使用
    shared_ptr
    登录后复制
    的线程都遵守相同的加锁规则,容易出错。

    // 假设MyExternalObject不是线程安全的
    class MyExternalObject { /* ... */ };
    
    std::shared_ptr<MyExternalObject> global_obj = std::make_shared<MyExternalObject>();
    std::mutex global_obj_mutex;
    
    void thread_func() {
        std::lock_guard<std::mutex> lock(global_obj_mutex);
        // 安全地访问 global_obj
        // global_obj->some_method();
    }
    登录后复制

选择哪种方法取决于对象的特性、访问模式以及对性能的要求。通常,内部互斥锁或不可变对象是更健壮和易于维护的选择。

在多线程中,何时需要对
shared_ptr
登录后复制
本身进行原子操作或加锁?

我们已经知道

shared_ptr
登录后复制
的引用计数是原子性的,但
shared_ptr
登录后复制
实例本身(即存储指针和控制块的那个变量)的并发修改却不是。那么,在哪些情况下我们需要对
shared_ptr
登录后复制
这个变量本身进行原子操作或加锁呢?

这主要发生在当一个

shared_ptr
登录后复制
变量(比如一个全局变量、一个类成员或者一个函数静态变量)在多个线程之间被重新赋值重置交换时。换句话说,当多个线程可能尝试改变
shared_ptr
登录后复制
变量指向谁的时候,就需要保护这个
shared_ptr
登录后复制
变量本身。

考虑以下场景:

  1. 共享的配置指针: 你的应用程序可能有一个全局的

    shared_ptr<Config> current_config;
    登录后复制
    ,用于指向当前的配置对象。当配置更新时,某个线程会创建新的
    Config
    登录后复制
    对象,并赋值给
    current_config
    登录后复制

    std::shared_ptr<Config> current_config; // 全局或共享的shared_ptr
    
    void update_config_thread() {
        auto new_config = std::make_shared<Config>(/* new parameters */);
        // 在这里,对 current_config 的赋值操作需要保护
        current_config = new_config;
    }
    
    void read_config_thread() {
        // 读取 current_config 也需要保护,以获取一致的视图
        std::shared_ptr<Config> local_config = current_config; // 拷贝操作
        // 使用 local_config
    }
    登录后复制

    在这种情况下,

    current_config = new_config;
    登录后复制
    这个赋值操作不是原子的。它涉及多个步骤(递减旧指针的引用计数,递增新指针的引用计数,更新内部指针)。如果另一个线程同时尝试读取
    current_config
    登录后复制
    或对其进行赋值,就可能导致数据损坏或不确定的行为。

  2. 资源池中的元素替换: 比如一个

    shared_ptr<Connection> active_connection;
    登录后复制
    ,当连接断开时,某个线程可能会将其重置为
    nullptr
    登录后复制
    ,或者替换为新的连接。

解决方案:

  • C++20及更高版本:使用

    std::atomic<std::shared_ptr<T>>
    登录后复制
    C++20引入了
    std::atomic<std::shared_ptr<T>>
    登录后复制
    ,它提供了对
    shared_ptr
    登录后复制
    实例本身的原子操作,如赋值、加载、交换等。这是最推荐的方式,因为它简洁且高效。

    #include <atomic> // C++20
    
    std::atomic<std::shared_ptr<Config>> current_config_atomic;
    
    void update_config_atomic_thread() {
        auto new_config = std::make_shared<Config>(/* new parameters */);
        current_config_atomic.store(new_config); // 原子赋值
    }
    
    void read_config_atomic_thread() {
        std::shared_ptr<Config> local_config = current_config_atomic.load(); // 原子加载
        // 使用 local_config
    }
    登录后复制
  • C++17及以前版本:使用

    std::mutex
    登录后复制
    手动保护。 在C++20之前,你需要用一个
    std::mutex
    登录后复制
    来显式地保护所有对
    shared_ptr
    登录后复制
    变量本身的读写操作。

    std::shared_ptr<Config> current_config_legacy;
    std::mutex config_mutex;
    
    void update_config_legacy_thread() {
        auto new_config = std::make_shared<Config>(/* new parameters */);
        std::lock_guard<std::mutex> lock(config_mutex);
        current_config_legacy = new_config; // 赋值操作在锁保护下进行
    }
    
    void read_config_legacy_thread() {
        std::shared_ptr<Config> local_config;
        {
            std::lock_guard<std::mutex> lock(config_mutex);
            local_config = current_config_legacy; // 拷贝操作在锁保护下进行
        }
        // 使用 local_config
    }
    登录后复制

记住,这里保护的是

shared_ptr
登录后复制
变量本身,而不是它所指向的对象。如果
Config
登录后复制
对象内部的数据也是可变的且需要多线程访问,那么
Config
登录后复制
对象本身也需要如前所述的内部同步机制。这是一个分层保护的概念:首先确保
shared_ptr
登录后复制
实例的更新是原子的,然后确保
shared_ptr
登录后复制
所指向的对象内部的数据访问也是线程安全的。

以上就是C++shared_ptr与多线程环境安全使用方法的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号