`std::shared_ptr` 的复制构造函数是原子的还是`reset()`?
Is the copy constructor of `std::shared_ptr` atomic versus `reset()`?
这是一个验证问题,以确保我得到的细节是正确的——欢迎语言律师。
我想知道我可以在下面的代码中使用std::shared_ptr
而不需要用atomic_shared_ptr
重写。该示例已被简化,但本质是 example
的单个实例中可能存在的竞争条件,介于 (*1) shared_ptr
的复制构造函数和 (*2) 对 [=16= 的调用之间].
请注意,p
的普通指针在这里不起作用。如果 p
在测试和调用 some_predicate
之间变为 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
returns 时,删除器在线程 *1 中运行。 (T
的幂等性在这里发挥作用。)
reset
在复制之前生效,所以p_transient
初始化为空。删除器运行在线程*2 before reset
returns.
我无法摆脱在这里不劳而获的感觉,所以我决定写下这个问题。有什么我想念的吗?
P.S。这就是我所缺少的。 shared_ptr
并不特别。不知怎的,我认为它会,也许是因为我之前已经实现了智能指针(太多)次。共享指针,特别是当还有弱指针时,几乎需要对其(隐藏的)共享状态进行互斥保护。我认为保护必须涵盖整个对象,但事实并非如此。
感谢回复者对标准的引用。数据竞争导致未定义行为的一般规则是 1.10/27 "Multi-threaded executions and data races [intro.multithread]"。特别是,这意味着在这种情况下可能会违反后置条件。
为了让#1 和#2 同时执行,您必须在两个不同的线程中调用 example::f
。如果它们在不同的 example
个实例上,那么 example::p
也会是不同的实例,所以没有问题。
如果它们在 same example
实例上,那么您违反了关于标准库竞争条件的 C++ 一般规则。如果您访问不同的对象 实例 ,您只能(通常)保证不会出现竞争条件。所以你可以 push_back
在两个不同的线程中的两个不同的 vector
上,但不能 same vector
.
shared_ptr
提供同样的保证。只要您不尝试从两个不同的线程访问同一个 shared_ptr
实例,就可以了。一旦你这样做了,所有的赌注都取消了。
atomic shared_ptr
functions 适用于希望从不同线程以原子方式操作同一对象的情况。
例如:
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 race cases: what could possibly go wrong? 发誓。他列出了可能出错的事情。
一个例子是,如果您写入一个内存位置,实际上允许编译器使用此内存 space 来保存溢出的寄存器。它不会经常发生,但它可能会发生。上面提到的博客是一个极端的例子,这种形式的数据竞赛无意中发射了核导弹! (希望真正的核导弹发射计算机更强大一点!)
如果你想让两个线程与一个数据交互,你必须防止数据竞争。这通常使用互斥体或原子来完成。
这是一个验证问题,以确保我得到的细节是正确的——欢迎语言律师。
我想知道我可以在下面的代码中使用std::shared_ptr
而不需要用atomic_shared_ptr
重写。该示例已被简化,但本质是 example
的单个实例中可能存在的竞争条件,介于 (*1) shared_ptr
的复制构造函数和 (*2) 对 [=16= 的调用之间].
请注意,p
的普通指针在这里不起作用。如果 p
在测试和调用 some_predicate
之间变为 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
returns 时,删除器在线程 *1 中运行。 (T
的幂等性在这里发挥作用。) reset
在复制之前生效,所以p_transient
初始化为空。删除器运行在线程*2 beforereset
returns.
我无法摆脱在这里不劳而获的感觉,所以我决定写下这个问题。有什么我想念的吗?
P.S。这就是我所缺少的。 shared_ptr
并不特别。不知怎的,我认为它会,也许是因为我之前已经实现了智能指针(太多)次。共享指针,特别是当还有弱指针时,几乎需要对其(隐藏的)共享状态进行互斥保护。我认为保护必须涵盖整个对象,但事实并非如此。
感谢回复者对标准的引用。数据竞争导致未定义行为的一般规则是 1.10/27 "Multi-threaded executions and data races [intro.multithread]"。特别是,这意味着在这种情况下可能会违反后置条件。
为了让#1 和#2 同时执行,您必须在两个不同的线程中调用 example::f
。如果它们在不同的 example
个实例上,那么 example::p
也会是不同的实例,所以没有问题。
如果它们在 same example
实例上,那么您违反了关于标准库竞争条件的 C++ 一般规则。如果您访问不同的对象 实例 ,您只能(通常)保证不会出现竞争条件。所以你可以 push_back
在两个不同的线程中的两个不同的 vector
上,但不能 same vector
.
shared_ptr
提供同样的保证。只要您不尝试从两个不同的线程访问同一个 shared_ptr
实例,就可以了。一旦你这样做了,所有的赌注都取消了。
atomic shared_ptr
functions 适用于希望从不同线程以原子方式操作同一对象的情况。
例如:
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 race cases: what could possibly go wrong? 发誓。他列出了可能出错的事情。
一个例子是,如果您写入一个内存位置,实际上允许编译器使用此内存 space 来保存溢出的寄存器。它不会经常发生,但它可能会发生。上面提到的博客是一个极端的例子,这种形式的数据竞赛无意中发射了核导弹! (希望真正的核导弹发射计算机更强大一点!)
如果你想让两个线程与一个数据交互,你必须防止数据竞争。这通常使用互斥体或原子来完成。