丢弃

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

为了做到这一点,我们可以实现 Drop

基本上,我们需要

  1. 减少引用计数
  2. 如果只剩下对数据的唯一引用,那么
  3. 原子地栅栏数据,以防止数据的使用和删除的重排序
  4. 丢弃内部数据

首先,我们需要访问 ArcInner

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

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

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

然后我们需要创建一个原子栅栏,以防止数据的使用和删除的重排序。正如在 标准库对 Arc 的实现 中描述的那样

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

正如 Boost 文档 中解释的那样,

重要的是,要强制执行在一个线程中(通过现有引用)对对象的任何可能的访问都发生在在不同线程中删除对象之前。这是通过在丢弃引用后执行 “release” 操作(显然,通过此引用对对象的任何访问都必须在此之前发生),以及在删除对象之前执行 “acquire” 操作来实现的。

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

还要注意,这里的 Acquire 栅栏可能可以用 Acquire 加载代替,这可以在高竞争情况下提高性能。 请参阅 2

为此,我们执行以下操作

#![allow(unused)]
fn main() {
use std::sync::atomic::Ordering;
use std::sync::atomic;
atomic::fence(Ordering::Acquire);
}

最后,我们可以丢弃数据本身。我们使用 Box::from_raw 来丢弃装箱的 ArcInner<T> 及其数据。这需要一个 *mut T 而不是一个 NonNull<T>,所以我们必须使用 NonNull::as_ptr 进行转换。

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

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

现在,让我们将所有这些都包装在 Drop 实现中

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