是“std::shared_ptr”atomic与“reset()”的复制构造函数

Is the copy constructor of `std::shared_ptr` atomic versus `reset()`?

本文关键字:复制 构造函数 reset atomic shared ptr std      更新时间:2023-10-16

这是一个验证问题,以确保我得到了正确的细节—欢迎语言律师。

我想知道我可以在下面的代码中使用std::shared_ptr,而不需要用atomic_shared_ptr重写它。该示例已被简化,但本质是example的单个实例中(*1(shared_ptr的复制构造函数和(*2(对reset()的调用之间可能存在竞争条件。

请注意,p的纯指针在这里不起作用。如果p在测试和对some_predicate的调用之间变为null,那么您将间接使用null指针。这就是首先使用shared_ptr的原因。我想确保我真的解决了比赛条件,而不是简单地把它转移到其他地方。

(这不是问题的重点,但乍一看,这段代码可能是错误的。T的行为是幂等的。一旦p完成了它的工作,就不再需要它了。(

template< class T >
class example
{
    shared_ptr< T > p ;
public:
    example()
        : p( make_shared( T() ) )
    {}
    void f()
    {
        shared_ptr< T > p_transient(p) ; // *1
        if ( p_transient && p_transient -> some_predicate() )
        {
            p.reset() ; // *2
        }
    }
};

假设(*1(和(*2(同时执行。我能想到比赛的两个可能结果。(这两种情况下的代码都是正确的。(我的问题是,这是否是仅的情况:

  • 副本在reset之前生效,因此p_transient使T的成员实例保持活动状态。当f返回时,deleter在线程*1中运行。(T的幂等性在这里发挥作用。(
  • reset在复制之前生效,因此p_transient初始化为空。在reset返回之前,deleter在线程*2中运行

我无法摆脱我在这里不劳而获的感觉,所以我决定写下这个问题。我缺了什么吗?


附言:这是我错过的。shared_ptr并不特别。不知怎么的,我认为会的,也许是因为我以前实现过(太多次(智能指针。共享指针,尤其是在存在弱指针的情况下,几乎需要对其(隐藏的(共享状态进行互斥保护。我认为保护一定涵盖了整个物体,但事实并非如此。

感谢响应者对该标准的参考。数据竞赛导致未定义行为的一般规则是1.10/27"多线程执行和数据竞赛[interr.multithreads]"。特别是,这意味着在这种情况下可能会违反后置条件。

为了使#1和#2同时执行,必须在两个不同的线程中调用example::f。如果这些在不同的example实例上,那么example::p也将是不同的实例,所以没有问题。

如果这些位于同一example实例上,则您违反了C++关于标准库竞争条件的一般规则。只有当您访问不同的对象实例时,才能(通常(保证您不受种族条件的影响。因此,您可以在两个不同线程中的两个不同的vector上使用push_back,但不能使用相同的vector

shared_ptr提供同样的保证。只要您不试图从两个不同的线程访问同一个shared_ptr实例,就可以了。一旦你这样做,所有的赌注都会被取消。

原子shared_ptr函数适用于希望从不同线程以原子方式操作同一对象的情况。

例如:

shared_ptr< T > p_transient(atomic_load(&p));
if ( p_transient && p_transient -> some_predicate() )
{
    atomic_store(&p, shared_ptr<T>());
}

或者,您可以将f封装在互斥对象或类似对象中。这也意味着您不必使用shared_ptr,因为您可能的破坏也包含在互斥体中。

您所看到的被称为数据竞赛。任何时候,一个线程可以写入某些数据,而另一个线程可能读取或写入该数据,这就是所谓的数据竞赛。

数据竞赛是未定义的行为。这意味着可能发生的事情没有限制。我对博客文章Benign种族案例发誓:可能出了什么问题?因为这些事情。他列出了一系列可能出错的事情。

一个例子是,如果向内存位置写入,编译器实际上可以使用这个内存空间来保存溢出的寄存器。这种情况并不经常发生,但它可以发生。上面提到的博客展示了一个极端的例子,这种形式的数据竞赛无意中发射了一枚核导弹!(希望真正的核导弹发射计算机更健壮!(

如果要让两个线程与一段数据交互,则必须防止数据争用。这通常是通过互斥或原子来完成的。