【问题标题】:C++11 atomics and intrusive shared pointer reference countC++11 原子和侵入式共享指针引用计数
【发布时间】:2012-04-22 14:21:31
【问题描述】:

我正在编写侵入式共享指针,并且我正在使用 C++11 <atomic> 工具作为引用计数器。以下是我的代码的相关片段:

//...
mutable std::atomic<unsigned> count;
//...

void
SharedObject::addReference() const
{
    std::atomic_fetch_add_explicit (&count, 1u,
        std::memory_order_consume);
}

void
SharedObject::removeReference() const
{
    bool destroy;

    destroy = std::atomic_fetch_sub_explicit (&count, 1u,
        std::memory_order_consume) == 1;

    if (destroy)
        delete this;
}

我首先从memory_order_acquirememory_order_release 开始,但后来我说服自己memory_order_consume 应该足够好。经过进一步考虑,在我看来,即使memory_order_relaxed 也应该可以工作。

现在的问题是我是否可以使用memory_order_consume 进行操作,或者我可以使用较弱的排序 (memory_order_relaxed) 还是应该使用更严格的排序?

【问题讨论】:

  • 由于计数器本质上充当delete 语句的递归锁,我想说addReference 中的“获取”和removeReference 中的“释放”是正确的顺序.但是你的addReference 也应该确保计数器不为零!
  • @KerrekSB:在对象首次创建后,在分配给SharedPtr&lt;&gt; 之前,addReference() 中的计数器可能为零。获取/释放语义似乎应该始终有效。但是是否可以使用较弱的排序约束?为什么不呢?
  • 关于零:假设 refcount 为 1。现在线程 1 想要删除对象并调用减法。如果此时线程 2 想要增加线程计数,它将从零递增到一,但线程 1 将继续删除该对象。应该避免这种情况。
  • @KerrekSB:这听起来像是addReference()/removeReference() 不平衡或对SharedPtr 实例的不受保护的访问,我认为这是未定义的行为。 SharedPtrs 作为成员本身需要受到互斥锁等的保护,与其封闭类的其他成员一样。
  • @KerrekSB "我会说 addReference 中的“获取”" 获取什么版本?

标签: c++ c++11 shared-ptr atomic


【解决方案1】:
void
SharedObject::addReference() const
{
    std::atomic_fetch_add_explicit (&count, 1u, std::memory_order_relaxed);
}

void
SharedObject::removeReference() const
{
    if ( std::atomic_fetch_sub_explicit (&count, 1u, std::memory_order_release) == 1 ) {
         std::atomic_thread_fence(boost::memory_order_acquire);
         delete this;
    }
}

您想使用atomic_thread_fence 使得delete 严格在fetch_sub 之后。 Reference

引用链接文本:

增加引用计数器总是可以用 memory_order_relaxed:只能形成对对象的新引用 从现有的引用,并从一个现有的引用传递 到另一个线程必须已经提供了任何所需的同步。

在一个对象中强制执行任何可能的访问权限是很重要的 线程(通过现有引用)在删除之前发生 对象在不同的​​线程中。这是通过“释放”来实现的 删除引用后的操作(通过 这个引用显然必须发生在之前),以及“获取” 删除对象之前的操作。

可以将 memory_order_acq_rel 用于 fetch_sub 操作,但这会导致不必要的“获取”操作,当 参考计数器尚未达到零,可能会施加性能 罚款。

【讨论】:

  • 虽然我已经接受了答案并以这种方式实现了我的侵入式指针,但引用让我思考了一下,这是另一个问题。将减量放宽到memory_order_relaxed 并让if() 的真正分支使用memory_order_acq_rel 怎么样?
  • 用你的_relaxed,然后_acq_rel,没有严格的fetch_sub(_relaxed)fence(_acq_rel)然后delete的顺序。您可能对此page 感兴趣。
  • 好的,看完页面我想我明白了它不可能是fetch_sub(_relaxed)。这是因为任何_relaxed 操作根本没有针对其他操作进行排序。但是fetch_sub(_consume)fence(_acq_rel) 呢?在我看来,fetch_sub(_consume) 构成了一个编译器重新排序障碍,fence(_acq_rel) 针对机器的其余部分对所有加载和存储进行了排序,以下delete 将具有一致的内存视图。还是我错了?
  • 这里有两点:1) _consume 用于指针,使得_consume 语句之后的任何与指针相关的操作 都不能移动到它之前。你不想在这里使用_consume。 2)如果您在这里谈论的是fetch_sub(_acquire)(而不是_consume),则_acquire 变量不会严格在fetch_sub 之后对delete 构成障碍,但反之亦然。
  • 好的,我想我现在基本明白了。我错过了来自cppreference 的这一点“从受影响的内存位置读取的带有数据依赖关系的读取无法在加载之前重新排序;其他读取可以是。在大多数平台上,这只会影响编译器优化。 "这对我来说是非常有启发性的讨论。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-25
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
  • 1970-01-01
  • 2013-11-27
  • 1970-01-01
相关资源
最近更新 更多