You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

shared_ptr线程不安全程度探究:标准是否强制内存顺序保证?

我希望有人能帮我验证对C++标准的理解:

核心问题

C++标准是否强制要求:shared_ptr的析构函数必须保证,对共享对象的访问操作happens-before其他线程中该对象的析构函数调用?


背景与代码示例

众所周知,shared_ptr仅通过引用计数管理对象生命周期,其管理的共享对象本身并非线程安全——共享对象仍通过普通指针访问,shared_ptr无法提供额外的线程安全保护。

我原本认为以下代码不存在数据竞争:

#include <memory>
#include <semaphore>
#include <thread>

void foo(std::shared_ptr<int>* in, std::binary_semaphore* flag, int* out)
{
    int temp;
    {
        std::shared_ptr<int> in_copy = *in; // 拷贝shared_ptr

        flag->release(); // 通知主线程拷贝完成

        temp = *in_copy; // 读取共享对象的值

        // in_copy 在此处析构
    }

    *out = temp; // 将读取的值写入out
}

int bar()
{
    int out_value;
    std::binary_semaphore flag{ 0 };
    std::thread t1;
    {
        // 创建shared_ptr并传递给线程
        std::shared_ptr<int> in_value = std::make_shared<int>(15);
        t1 = std::thread{ &foo, &in_value, &flag, &out_value };

        flag.acquire(); // 等待线程完成shared_ptr的拷贝

        // in_value 在此处析构
    }
    t1.join(); // 等待线程执行完毕

    return out_value;
}

潜在的数据竞争担忧

我特别担心*out = temp;会引发数据竞争——如果编译器允许将temp = *in_copy;的读取操作重排至shared_ptr析构之后,代码逻辑会等价于:

void foo(std::shared_ptr<int>* in, std::binary_semaphore* flag, int* out)
{
    // 拷贝shared_ptr的底层逻辑
    __refcount* in_refcount = in->get_refcount();
    in_refcount->increment();
    int* in_copy = in->get();

    flag->release(); // 通知主线程拷贝完成

    if (in_refcount->decrement_and_check_zero())
    {
         *out = *in_copy;
         destroy in_copy;
    }
    else
    {
        *out = *in_copy;
    }
}

此时else分支存在明显的数据竞争:在in_refcount->decrement_and_check_zero()与*in_copy的读取操作之间,其他线程可能已经销毁了共享对象,导致读取已结束生命周期的对象。

主流实现的处理方式

目前三大主流标准库实现(GCC libstdc++、Clang libc++、MSVC STL)均不允许这种重排——它们将引用计数的递减操作设为release语义的原子操作,在共享对象的读取操作与引用计数递减之间建立了happens-before关系,从而确保共享对象的访问与析构操作在多线程间的顺序性。

标准条款的疑问

但关键问题在于:C++标准是否强制要求这种内存顺序保证?我查阅标准后发现相关描述存在明显空白:

  • [util.smartptr.shared]仅说明shared_ptr实现共享所有权语义,use_count()的修改不会引入数据竞争;
  • [util.smartptr.shared.dest]仅说明shared_ptr销毁后,其他共享实例的use_count()会减1。

这些描述仅赋予引用计数类似宽松原子操作的语义,但并未明确其为原子操作,因此无法依赖标准中基于原子操作的线程间顺序规则;同时shared_ptr也未像mutex::lock()/unlock()那样被明确列为具备同步语义的操作。

最终困惑

综上,我未在标准中找到任何条款,能够保证shared_ptr所管理对象的访问操作,与其他线程中该对象的析构函数调用之间存在happens-before关系。这意味着使用shared_ptr时,每次其出作用域都需要额外的同步操作(如内存屏障)来避免对象访问与析构间的数据竞争——这显然不合理,会让shared_ptr在多数场景下几乎无用,但我始终无法找到标准中强制要求该内存顺序保证的依据,希望有人指出我的理解错误。


内容的提问来源于stack exchange,提问作者user19232978

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 14:05:16