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<T>
是一些所谓的“线程兼容”类,这意味着只要std::shared_ptr<T>
的每个instance 只能有一个线程在给定点调用其成员函数及时,这样的成员函数调用不会导致竞争条件,即使多个线程正在访问彼此共享所有权的shared_ptr
s。
std::shared_ptr<T>
不是 线程安全 类;一个线程调用std::shared_ptr<T>
实例的非const
方法是不安全的,而另一个线程也在访问同一个实例。如果您需要潜在的并发读写不竞争,请使用互斥锁同步它们。
【讨论】:
我可以询问证实这一点的来源吗?并不是我不相信你,而是我只是想了解更多并阅读完整的源代码以更好地理解。 @nhtrnm eel.is/c++draft/res.on.data.races 暗示标准库类在通过其成员函数访问时是线程兼容的。 eel.is/c++draft/util.smartptr.shared#4 暗示这延伸到shared_ptr
s,即使它们共享对象的所有权。以上是关于shared_ptr 上的原子操作的主要内容,如果未能解决你的问题,请参考以下文章
shared_ptrs 是不是由于引用计数器原子递增/递减而遇到缓存未命中?