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 ahhstd::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>());
}