为什么在删除原子引用计数智能指针中的数据之前需要获取屏障?

Sco*_*son 9 c++ multithreading boost atomic shared-memory

Boost提供了一个原子引用计数共享指针示例

以下是相关的代码段以及所使用的各种排序的说明:

class X {
public:
  typedef boost::intrusive_ptr<X> pointer;
  X() : refcount_(0) {}

private:
  mutable boost::atomic<int> refcount_;
  friend void intrusive_ptr_add_ref(const X * x)
  {
    x->refcount_.fetch_add(1, boost::memory_order_relaxed);
  }
  friend void intrusive_ptr_release(const X * x)
  {
    if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
      boost::atomic_thread_fence(boost::memory_order_acquire);
      delete x;
    }
  }
};
Run Code Online (Sandbox Code Playgroud)

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

在删除不同线程中的对象之前,必须在一个线程(通过现有引用)中强制执行对对象的任何可能访问.这是通过在删除引用之后的"释放"操作(通过此引用对对象的任何访问必须明显发生之前)和在删除对象之前的"获取"操作来实现的.

可以将memory_order_acq_rel用于fetch_sub操作,但是当引用计数器尚未达到零时,这会导致不需要的"获取"操作,并且可能会造成性能损失.

我无法理解为什么memory_order_acquire屏障在之前是必要的delete x手术.具体来说,如何在不违反单线程语义的情况下对编译器/处理器重新排序和测试值delete x之前的内存操作是否安全?fetch_subx == 1

编辑我想,我的问题不是很清楚.这是一个改写版本:

读取x(x->refcount_.fetch_sub(1, boost::memory_order_release) == 1)和delete x操作之间的控制依赖性是否会提供任何排序​​保证?即使考虑单线程程序,编译器/处理器是否可以重新排序与delete x之前的操作相对应的指令fetch_sub在对比与?如果答案尽可能低,并且包含一个示例场景,其中删除操作被重新排序(不影响单线程语义),从而说明需要保留排序,这将非常有用.

T.C*_*.C. 6

考虑两个线程,每个线程持有一个对象的引用,这是最后两个引用:

------------------------------------------------------------
        Thread 1                              Thread 2
------------------------------------------------------------
   // play with x here

   fetch_sub(...)                            
                                            fetch_sub(...)
   // nothing
                                            delete x;
Run Code Online (Sandbox Code Playgroud)

您必须确保线程1在//play with x here调用时对该对象所做的任何更改对于线程2都是可见的delete x;.为此,您需要一个获取栅栏,其中,连同memory_order_release上的fetch_sub()电话,以确保由线索1中所做的更改将是可见的.

  • 如果保证 x 是指向没有析构函数的 pod 的指针(我们可以只执行 free(x)),是否仍然需要获取屏障?free(x) 是否可以在 if (x.fetch_sub(...) == 1) 之前重新排序而不违反单线程执行的语义? (2认同)
  • @scott想象处理器缓存中的数据.它被修改,但没有写.然后将数据块发送到堆以进行回收.然后刷新缓存.热潮,你只是写信给你不拥有的记忆. (2认同)