C++ 'std::shared_ptr'的复制构造函数是原子的还是'reset()`?

C++ 'std::shared_ptr'的复制构造函数是原子的还是'reset()`?,c++,concurrency,shared-ptr,C++,Concurrency,Shared Ptr,这是一个验证问题,以确保我得到了正确的细节 我想知道我可以在下面的代码中使用std::shared\u ptr,而不需要用atomic\u shared\u ptr重写它。该示例已被简化,但其实质是示例的单个实例内可能存在的竞争条件,即(*1)共享的复制构造函数和(*2)调用重置() 请注意,p的普通指针在这里不起作用。如果p在测试和调用some_谓词之间变为null,那么您将间接指向一个null指针。这就是为什么要首先使用shared_ptr。我想确保我实际上解决了比赛条件,而不是简单地把它移

这是一个验证问题,以确保我得到了正确的细节

我想知道我可以在下面的代码中使用
std::shared\u ptr
,而不需要用
atomic\u shared\u ptr
重写它。该示例已被简化,但其实质是
示例
的单个实例内可能存在的竞争条件,即(*1)共享的复制构造函数
和(*2)调用
重置()

请注意,
p
的普通指针在这里不起作用。如果
p
在测试和调用
some_谓词
之间变为null,那么您将间接指向一个null指针。这就是为什么要首先使用
shared_ptr
。我想确保我实际上解决了比赛条件,而不是简单地把它移到别处

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

模板
课例
{
共享ptrp;
公众:
示例()
:p(使_共享(T())
{}
void f()
{
共享_ptrp_瞬态(p);//*1
if(p_transient&&p_transient->some_predicate())
{
p、 重置();//*2
}
}
};
假设(*1)和(*2)同时执行。我能想到两个可能的比赛结果。(这两种情况下,代码都是正确的。)我的问题是,是否只有这些情况:

  • 副本在
    重置之前生效,因此
    p_transient
    会使
    T
    的成员实例保持活动状态。当
    f
    返回时,删除程序在线程*1中运行。(这里起作用的是
    T
    的幂等性。)
  • reset
    在复制之前生效,因此
    p_transient
    初始化为空。在
    reset
    返回之前,删除程序在线程*2中运行
我无法摆脱这种感觉,我在这里得到了一些免费的东西,所以我决定写下这个问题。我遗漏了什么


还有,这是我错过的<代码>共享\u ptr
并不特别。不知何故,我认为这是可能的,也许是因为我已经实现了智能指针(太多)次了。共享指针,特别是当也有弱指针时,它们的(隐藏的)共享状态几乎需要互斥保护。我想保护一定涵盖了整个物体,但事实并非如此

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

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

如果这些代码在同一个<代码>示例>实例中,那么你违反了C++关于标准库竞争条件的一般规则。只有在访问不同的对象实例时,才能(通常)保证不受竞争条件的影响。因此,您可以在两个不同线程中的两个不同的

vector
s上
push_back
,但不是相同的
vector

shared_ptr
提供了同样的保证。只要您不试图从两个不同的线程访问相同的
共享\u ptr
实例,就可以了。一旦你这么做了,所有的赌注都输光了

当您希望从不同线程原子化地操作同一对象时,可以使用

例如:

shared_ptr< T > p_transient(atomic_load(&p));
if ( p_transient && p_transient -> some_predicate() )
{
    atomic_store(&p, shared_ptr<T>());
}
shared_ptrp_瞬态(原子负载(&p));
if(p_transient&&p_transient->some_predicate())
{
原子存储(&p,shared_ptr());
}

或者,您可以将
f
包装在互斥锁或类似的东西中。这也意味着您不必使用
共享\u ptr
,因为您可能的破坏也被包装在互斥锁中。

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

数据竞争是未定义的行为。这意味着可能发生的事情没有限制。我对这类事情的博客条目发誓。他列举了一系列可能出错的事情

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


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

澄清了这个问题,说明这是针对单个实例,而不是多个实例。关于库竞争的一般规则是什么。我拿出标准来查找
shared\u ptr
的后置条件,但我不知道在哪里可以找到确切的措辞。@eh9:一般规则由[res.on.data.races]规定。[util.smartpointer.shared]/4解释了有关数据竞争的
共享\u ptr
特定行为。我熟悉一般情况。问题是关于
std::shared\u ptr
提供的具体保证。@eh9 ahh
std::shared_ptr
不提供任何保证,这就是为什么我选择了常规案例。2.7.2.2第4条规定了您应如何处理共享ptr(“成员功能”)上的数据竞争
shared_ptr< T > p_transient(atomic_load(&p));
if ( p_transient && p_transient -> some_predicate() )
{
    atomic_store(&p, shared_ptr<T>());
}