Dropping

我们现在需要一种方法来减少引用计数,并在计数足够低时删除数据,否则数据将永远存在于堆中。

为此,我们可以实现Drop

基本上,我们需要:

  1. 减少引用计数

  2. 如果数据只剩下一个引用,则:

  3. 原子地隔离数据以防止数据的使用和删除的重新排序,然后:

  4. 删除内部数据

首先,我们需要访问 ArcInner

  1. let inner = unsafe { self.ptr.as_ref() };

现在,我们需要减少引用计数。 为了简化我们的代码,我们还可以在 fetch_sub 的返回值(减少它的引用计数之前的值)不等于 1 时返回(当我们不是对数据的最后一个引用时会发生这种情况)。

  1. if inner.rc.fetch_sub(1, Ordering::Relaxed) != 1 {
  2. return;
  3. }

然后我们需要创建一个原子原子围栏,来防止重新排序数据的使用和删除数据。 如标准库的Arc实现中所述:

需要此围栏以防止重新排序使用数据和删除数据。 因为它被标记为Release,所以引用计数的减少与这个Acquire 围栏同步。 这意味着数据的使用发生在减少引用计数之前,减少引用计数发生在此围栏之前,发生在删除数据之前。

Boost文档中所述,

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

特别是,虽然Arc的内容通常是不可变的,但可以对Mutex之类的内容进行内部写入。 由于Mutex在被删除时不会被获取,我们不能依赖它的同步逻辑来使线程A中的写入对线程B中运行的析构函数可见。

另请注意,此处的Acquire围栏可能会替换为Acquire负载,这可以提高在竞争激烈的情况下的性能。 见2

为此,我们执行以下操作:

  1. atomic::fence(Ordering::Acquire);

我们需要导入std::sync::atomic本身:

  1. use std::sync::atomic;

最后,我们可以删除数据本身。 我们使用Box::from_raw来删除装箱的ArcInner<T>及其数据。 它接受一个*mut T而不是NonNull<T>,因此我们必须使用NonNull::as_ptr进行转换。

  1. unsafe { Box::from_raw(self.ptr.as_ptr()); }

这是安全的,因为我们知道我们有指向ArcInner的最后一个指针,并且它的指针是有效的。

现在,让我们把这些都包含在Drop实现中:

  1. impl<T> Drop for Arc<T> {
  2. fn drop(&mut self) {
  3. let inner = unsafe { self.ptr.as_ref() };
  4. if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
  5. return;
  6. }
  7. // This fence is needed to prevent reordering of the use and deletion
  8. // of the data.
  9. atomic::fence(Ordering::Acquire);
  10. // This is safe as we know we have the last pointer to the `ArcInner`
  11. // and that its pointer is valid.
  12. unsafe { Box::from_raw(self.ptr.as_ptr()); }
  13. }
  14. }