shared_ptr 上的原子操作

Posted

技术标签:

【中文标题】shared_ptr 上的原子操作【英文标题】:Atomic operations on shared_ptr 【发布时间】:2019-04-18 20:48:36 【问题描述】:

假设我有shared_ptr<T> a 和两个线程同时运行:

a.reset();

还有一个:

auto b = a;

如果操作是原子的,那么我要么以两个空的 shared_ptrs 结束,要么以 a 为空而 b 指向 a 所指向的内容。我对这两种结果都很好,但是,由于指令的交错,这些操作可能不是原子的。有什么办法可以保证吗?

更准确地说,我只需要 a.reset() 是原子的。

UPD:正如 cmets 中所指出的,如果我没有得到更具体的信息,我的问题就是愚蠢的。使用互斥体可以实现原子性。但是,我想知道在shared_ptr 的实现级别上,事情是否已经处理好了。从 cppreference.com,复制赋值和复制构造函数是线程安全的。所以auto b = a 可以在没有锁的情况下运行。但是,从 this 来看,尚不清楚 a.reset() 是否也是线程安全的。

UPD1:如果有一些文档指定 shared_ptr 的哪些方法是线程安全的,那就太好了。来自 cppreference:

如果多个执行线程在没有同步的情况下访问同一个 shared_ptr,并且其中任何一个访问使用 shared_ptr 的 非常量成员函数,那么就会发生数据竞争

我不清楚哪些方法是非常量的。

【问题讨论】:

如果您正在处理线程,那么您应该听说过信号量互斥体条件变量。如果没有,那么是时候做更多的研究了。 @Someprogrammerdude 是的,我需要更具体一些。当然,我可以使用这些,但我想知道这些操作对于 shared_ptr 是否是线程安全的。让我具体说明一下。 shared_ptr 在线程方面与任何其他对象一样。如果您正在阅读和写作,则需要同步。 @NathanOliver 我认为这不是真的,因为复制构造函数或赋值等操作实现了某种程度的同步,这让我相信其他函数可能已经保证了线程安全。我同意对于任何对象,我都可以在其之上添加另一层同步。如果编译器能保证,我不想做额外的工作。 此外,在效率方面,如果 reset() 存在 shared_ptr 底层同步,我无法相信我会击败它。 【参考方案1】:

让其他线程使用weak_ptr。弱指针上的 lock() 操作被证明是原子的。

创建:

std::shared_ptr<A> a = std::make_shared<A>();
std::weak_ptr<A> a_weak = std::weak_ptr<A>(a);

线程 1:

a.reset();

线程 2:

b = a_weak.get();
if (b != nullptr)

    ...

【讨论】:

【参考方案2】:

std::shared_ptr&lt;T&gt; 是一些所谓的“线程兼容”类,这意味着只要std::shared_ptr&lt;T&gt; 的每个instance 只能有一个线程在给定点调用其成员函数及时,这样的成员函数调用不会导致竞争条件,即使多个线程正在访问彼此共享所有权的shared_ptrs。

std::shared_ptr&lt;T&gt; 不是 线程安全 类;一个线程调用std::shared_ptr&lt;T&gt; 实例的非const 方法是不安全的,而另一个线程也在访问同一个实例。如果您需要潜在的并发读写不竞争,请使用互斥锁同步它们。

【讨论】:

我可以询问证实这一点的来源吗?并不是我不相信你,而是我只是想了解更多并阅读完整的源代码以更好地理解。 @nhtrnm eel.is/c++draft/res.on.data.races 暗示标准库类在通过其成员函数访问时是线程兼容的。 eel.is/c++draft/util.smartptr.shared#4 暗示这延伸到shared_ptrs,即使它们共享对象的所有权。

以上是关于shared_ptr 上的原子操作的主要内容,如果未能解决你的问题,请参考以下文章

shared_ptrs 是不是由于引用计数器原子递增/递减而遇到缓存未命中?

CUDA: 原子操作

队列<T>上的原子操作?

AtomicLong可以被原子地读取和写入的底层long值的操作

访问由 shared_ptr 持有的类的原子成员

转Java线程安全