安全地使用 auto_ptr 交换对象而不锁定在多线程环境中?

Posted

技术标签:

【中文标题】安全地使用 auto_ptr 交换对象而不锁定在多线程环境中?【英文标题】:Safe to use auto_ptr to swap objects without locking in multithreaded environment? 【发布时间】:2013-01-05 19:08:28 【问题描述】:

我在堆上分配了一些很少修改但需要快速读取访问的数据结构。一个示例是在堆上分配的结构,许多线程以只读方式非常频繁地访问该结构。需要定期重写此结构并避免锁定争用我想知道使用auto_ptr 基本上允许已获得引用的线程继续处理直到它们完成但允许作者制作副本是否安全结构体,重写它并快速将指针与结构体的新auto_ptr 实例交换。

我从 Java 中的 CopyOnWriteArrayList 得到这个想法,并希望在 C++ 中执行类似的性能。

【问题讨论】:

【参考方案1】:

std::auto_ptr 在调用非 const 成员(如您所建议的 reset())时没有任何线程安全保证。此外,std::unique_ptr 也不是auto_ptr 的替代品,因为auto_ptr 已被有效弃用。

std::shared_ptr确实提供这样的线程安全保证。

shared_ptr 通常向您保证的是引用计数是以原子方式操作的,这意味着在创建shared_ptr 的副本时,您可以安全地避免数据竞争,前提是当前没有人修改 @ 987654338@就在那一刻。

考虑您的shared_ptr 的以下用例。您有一个全局shared_ptr<string> sharedString;,它当前指向std::string 的一个实例。许多线程调用get() 来检索指向字符串的指针是安全的。他们也可以像这样创建自己的共享指针:shared_ptr<string> myString = sharedString;,前提是没有人更改共享指针指向的内容。

现在,让我们返回并修复示例中存在的竞争条件。当需要更改全局共享指针指向的内容时,线程可能正在制作它的副本——这是一个问题,因为它可能使副本处于不一致的状态。因此,我们必须确保以原子方式更改和复制它。

幸运的是,shared_ptr 提供了许多 C++11 原子操作的特化:atomic_loadatomic_storeatomic_exchange

复制shared_ptr 时,使用atomic_load,更新shared_ptr 时,使用atomic_store

这样做很重要,原因有两个。

    atomic_* 操作为您提供了一种线程安全的方式来制作共享指针的副本。 制作副本意味着当全局共享指针更改时,您的副本仍指向旧字符串,因此您的代码不必担心它所操作的数据会在它不期望的时候发生更改。

现在,重要的是要注意,在 shared_ptr 上使用线程安全操作不会为其指向的类型提供任何线程安全性。您必须始终注意以线程安全的方式使用指向的对象。

【讨论】:

您有讨论shared_ptr 的线程安全保证的链接吗? 当然!线程安全性在标题 [util.smartptr.shared.atomic] 下的 C++ 标准中进行了解释。这是一个指向proposal 的链接,它添加了解释用法并提供一些代码的功能。 关于 gcc wo/c++11 扩展的类似机制的任何想法?我可以使用 gcc 内在函数而不是 c++11 的 atomic_* 功能吗? 实际上,再想一想,如果我保证在我的 x86_64 架构上将指针对齐到 8 个字节,那么读取和写入将是原子的,因此应该能够在没有任何类型的情况下换出指向 shared_ptr 的指针显式锁定,不是吗? @stgtscc 您可以使用原子指针类型,但在对象销毁方面仍然存在数据竞争。根据它的使用方式,您将面临use-after-free 的风险。【参考方案2】:

您可以使用 reset() 动态重新分配 auto_ptr,顺便说一下,它会破坏以前指向的对象实例。

但是,auto_ptr 已被 unique_ptr 弃用,它有一个 swap() 函数,似乎有点坚持你正在寻找的东西。

请记住,这些类不是线程安全的。

【讨论】:

以上是关于安全地使用 auto_ptr 交换对象而不锁定在多线程环境中?的主要内容,如果未能解决你的问题,请参考以下文章

C# 在多线程环境中,进行安全遍历操作

我们可以用两个或多个无锁容器原子地做一些事情而不锁定两者吗?

Berkeley DB:锁定记录而不读取它

有没有办法在多线程应用程序中安全地使用 errno? [复制]

在多用户访问环境中即时“锁定”记录

auto_ptr类